<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>주프링 블로그</title>
    <link>https://fladi.tistory.com/</link>
    <description>공부중인 학생입니다! 글에서 틀린 곳이 있으면 지적 부탁드립니다
블로그 이사 https://velog.io/@joohr1234</description>
    <language>ko</language>
    <pubDate>Sun, 28 Jun 2026 18:38:49 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>fladi</managingEditor>
    <image>
      <title>주프링 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/4809934/attach/a8fbda4a72bf47adb6773ad4bbff51f9</url>
      <link>https://fladi.tistory.com</link>
    </image>
    <item>
      <title>[JAVA] 백준 dp 부수기</title>
      <link>https://fladi.tistory.com/454</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9251 LCS - 골드5&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/9251&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/9251&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1747471673782&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    String a = br.readLine();
    String b = br.readLine();

    int[][] dp = new int[a.length()+1][b.length()+1];
    for (int i =0; i&amp;lt;a.length(); i++) {
        char c1 = a.charAt(i);
        int idx1 = i+1;

        for (int j =0; j&amp;lt;b.length(); j++) {
            char c2 = b.charAt(j);
            int idx2 = j+1;

            if (c1 == c2) {
                dp[idx1][idx2] = Math.max(dp[idx1][idx2], dp[idx1-1][idx2-1]+1);
            } else {
                dp[idx1][idx2] = Math.max(dp[idx1-1][idx2], dp[idx1][idx2-1]);

            }
        }
    }

    System.out.println(dp[a.length()][b.length()]);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유명한 LCS문제다. 점화식만 알고있다면 어렵지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;17404 RGB거리 2 - 골드4&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/17404&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/17404&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1743511258536&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int houseCnt = Integer.parseInt(br.readLine());

        int[][] cost = new int[houseCnt][3];
        int[][][] dp = new int[3][2][3]; //[첫번째 무슨색 채울건지][house 인덱스 % 2 값][rgb]

        //(0)입력받기
        for (int i = 0; i &amp;lt; houseCnt; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            for (int j = 0; j &amp;lt; 3; j++) {
                cost[i][j] = Integer.parseInt(st.nextToken());
            }
        }

        //(1)초기화 작업
        for (int i =0; i&amp;lt;3; i++) {
            for (int j =0; j&amp;lt;2; j++) {
                Arrays.fill(dp[i][j], 1000000);
            }
        }

        dp[0][0][0] = cost[0][0];
        dp[1][0][1] = cost[0][1];
        dp[2][0][2] = cost[0][2];

        //(2)dp배열 갱신
        for (int i =1; i&amp;lt;houseCnt; i++) {
            int currentIdx = i % 2;
            int prevIdx = (i+1) % 2;

            for (int j=0; j&amp;lt;3; j++) {
                dp[j][currentIdx][0] = Math.min(dp[j][prevIdx][1] + cost[i][0], dp[j][prevIdx][2] + cost[i][0]);
                dp[j][currentIdx][1] = Math.min(dp[j][prevIdx][0] + cost[i][1], dp[j][prevIdx][2] + cost[i][1]);
                dp[j][currentIdx][2] = Math.min(dp[j][prevIdx][0] + cost[i][2], dp[j][prevIdx][1] + cost[i][2]);
            }
        }

        //(3)최솟값 구하기
        int currentIdx = (houseCnt-1) % 2;
        int result = Integer.MAX_VALUE;

        result = Math.min(result, dp[0][currentIdx][1]);
        result = Math.min(result, dp[0][currentIdx][2]);
        result = Math.min(result, dp[1][currentIdx][0]);
        result = Math.min(result, dp[1][currentIdx][2]);
        result = Math.min(result, dp[2][currentIdx][0]);
        result = Math.min(result, dp[2][currentIdx][1]);

        System.out.println(result);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3차원 dp 배열로 해결했다. dp배열의 각 요소는 다음과 같음&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[3]: 빨강/초록/파랑 집으로 시작하는 경우&lt;/li&gt;
&lt;li&gt;[2]: 집의 인덱스를 %2 한 값 (모든 dp값을 저장할 필요가 없기 때문에 2개로 했다)&lt;/li&gt;
&lt;li&gt;[3]: (1~N)까지 채우고 현재 집을 칠한 색(RGB중 하나)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1106 호텔 - 골드4&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1106&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/1106&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;느낌은 배낭문제와 굉장히 유사했다. 풀이는 크게 2가지 방법이 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;특정 비용으로 늘릴 수 있는 최대 고객 수 구하기&lt;/li&gt;
&lt;li&gt;특정 고객 수를 위한 최소 비용 구하기&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제에 정확히 C명이 아니라 &quot;적어도 C명&quot;이라고 되어있기 때문에 C보다 높은 가장 최소 비용을 구해야한다. 그래서 1번과 2번방법 모두 column 범위를 크게 잡아줘야한다는 걸 알 수 있었다. 가장 처음에는 1번 방식으로 풀어봤다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1106 - 1번 방식 풀이&lt;/h4&gt;
&lt;pre id=&quot;code_1747421369736&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    StringTokenizer st = new StringTokenizer(br.readLine());

    int requiredCnt = Integer.parseInt(st.nextToken()); // x &amp;lt;= 1000
    int cityCnt = Integer.parseInt(st.nextToken()); // x &amp;lt;= 20

    int[][] dp = new int[2][requiredCnt * 100 + 1]; //특정 비용으로 늘릴 수 있는 최대 고객 수 구하기

    for (int i =0; i&amp;lt;cityCnt; i++) {
        st = new StringTokenizer(br.readLine());

        int cost = Integer.parseInt(st.nextToken());
        int customerCnt = Integer.parseInt(st.nextToken());

        int current = i % 2;
        int prev = (i + 1) % 2;

        for (int j=0; j&amp;lt;dp[0].length; j++) {
            if (dp[current][j] &amp;lt; dp[prev][j])
                dp[current][j] = dp[prev][j];

            if (j &amp;lt; cost) {
                continue;
            }

            dp[current][j] = Math.max(dp[current][j], dp[current][j-cost] + customerCnt);
        }
    }

    //C명 이상이 되는 비용을 찾으면 break =&amp;gt; 최소비용
    int tmp = (cityCnt-1) % 2;
    for (int i =0; i&amp;lt;dp[0].length; i++) {
        if (dp[tmp][i] &amp;gt;= requiredCnt) {
            System.out.println(i);
            break;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;requiredCnt를 만들기 위해 사용해야할 최대 금액은 100 * requiredCnt이다. (&quot;비용은 100 이하, 얻을 수 있는 고객수는 자연수&quot;라고 문제에 명시되어 있다) 이 값을 column 크기로 지정하여 특정 비용으로 얻을 수 있는 최대 고객수를 dp배열에 저장했다.&lt;/li&gt;
&lt;li&gt;row는 2개만 사용하여 아껴썼다.&lt;/li&gt;
&lt;li&gt;requiredCnt 이상을 얻을 수 있는 비용을 for 루프로 찾아서 바로 반환하면 답을 구할 수 있다. 금액 범위는 넉넉하게 잡아줬기 때문에 무조건 답은 출력된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1106 - 2번 방식 풀이&lt;/h4&gt;
&lt;pre id=&quot;code_1747468861979&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    StringTokenizer st = new StringTokenizer(br.readLine());

    int requiredCnt = Integer.parseInt(st.nextToken()); // x &amp;lt;= 1000
    int cityCnt = Integer.parseInt(st.nextToken()); // x &amp;lt;= 20

    int[][] dp = new int[2][requiredCnt+101]; //특정 고객수를 만들기 위한 최소 비용
    Arrays.fill(dp[0], 100_001);
    Arrays.fill(dp[1], 100_001);

    dp[0][0] = 0;
    dp[1][0] = 0;

    for (int i =0; i&amp;lt;cityCnt; i++) {
        st = new StringTokenizer(br.readLine());

        int cost = Integer.parseInt(st.nextToken());
        int customerCnt = Integer.parseInt(st.nextToken());

        int current = i % 2;
        int prev = (i + 1) % 2;

        for (int j=0; j&amp;lt;dp[0].length; j++) {
            if (dp[current][j] &amp;gt; dp[prev][j])
                dp[current][j] = dp[prev][j];

            if (j &amp;lt; customerCnt) {
                continue;
            }

            dp[current][j] = Math.min(dp[current][j], dp[current][j-customerCnt] + cost);
        }
    }

    int tmp = (cityCnt-1) % 2;
    int min = Integer.MAX_VALUE;
    for (int i =requiredCnt; i&amp;lt;dp[0].length; i++) {
        if (dp[tmp][i] &amp;lt; min) {
            min = dp[tmp][i];
        }
    }

    System.out.println(min);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;dp로 갱신해야할 고객 수 범위는 requiredCnt + 100이다. &quot;적어도 C명 이상&quot;이라고 명시되어 있기 때문에, &quot;정확히 C명&quot;인 비용보다 &quot;C명보다 큰 고객수&quot;일 때 비용이 더 낮을 수도 있다. 비용의 범위는 100이하이기 때문에 크기는 최대 requiredCnt+100밖에 되지 않는다.&lt;/li&gt;
&lt;li&gt;위 방식과 마찬가지로 row는 2개만 사용하여 아껴썼다.&lt;/li&gt;
&lt;li&gt;&quot;정확히 C명&quot;일 때의 비용부터 &quot;C명보다 큰 고객수&quot;일 때의 비용을 모두 비교하여 최솟값을 min에 넣어주고 출력해주면 답이 나온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;381&quot; data-origin-height=&quot;131&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pHyTj/btsN0JqlmVv/jnAZeV8fQerANNK4kHF75k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pHyTj/btsN0JqlmVv/jnAZeV8fQerANNK4kHF75k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pHyTj/btsN0JqlmVv/jnAZeV8fQerANNK4kHF75k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpHyTj%2FbtsN0JqlmVv%2FjnAZeV8fQerANNK4kHF75k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;381&quot; height=&quot;131&quot; data-origin-width=&quot;381&quot; data-origin-height=&quot;131&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;column 범위가 1번이 훨씬 크기 때문에 계산 횟수도 더 많다. 그래서 2번이 성능이 더 좋게 나오는 걸 확인할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1958 LCS3 - 골드4&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1958&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/1958&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1747472403969&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    String a = br.readLine();
    String b = br.readLine();
    String c = br.readLine();

    int[][][] dp = new int[a.length()+1][b.length()+1][c.length()+1];
    for (int i =0; i&amp;lt;a.length(); i++) {
        char c1 = a.charAt(i);
        int idx1 = i+1;

        for (int j =0; j&amp;lt;b.length(); j++) {
            char c2 = b.charAt(j);
            int idx2 = j+1;

            for (int r=0; r&amp;lt;c.length(); r++) {
                char c3 = c.charAt(r);
                int idx3 = r+1;

                //모두 같으면
                if (c1 == c2 &amp;amp;&amp;amp; c2 == c3) {
                    dp[idx1][idx2][idx3] = dp[idx1-1][idx2-1][idx3-1] + 1;
                } else {
                    //idx1은 고정이라고 생각하기
                    dp[idx1][idx2][idx3] = Math.max(dp[idx1][idx2-1][idx3], dp[idx1][idx2][idx3-1]);
                    dp[idx1][idx2][idx3] = Math.max(dp[idx1][idx2][idx3], dp[idx1-1][idx2][idx3]);
                }
            }
        }
    }

    System.out.println(dp[a.length()][b.length()][c.length()]);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3차원 dp를 써야할 것 같다는 느낌이 왔다. 3가지 글자를 동시에 비교하고 갱신해야한다는 점만 고려하면 2차원 LCS와 유사하게 풀이할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2133 타일 채우기 - 골드4&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2133&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/2133&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1747475001577&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    int n = Integer.parseInt(br.readLine());

    if (n % 2 == 1) {
        System.out.println(0);
        return;
    }

    int[] dp = new int[n+1];
    dp[0] = 1;

    //1. ||_ 3x2
    //2. _|| 3x2
    //3. =_ 3x2
    //4. __ |=| 3x4 3x6 3x8 ...
    //5. |=| __ 3x4 3x6 3x8 ...

    for (int i =2; i&amp;lt;dp.length; i+=2) {
        dp[i] = dp[i-2] * 3;

        if (i &amp;gt;3) {
            int tmp = 2;
            while (i - tmp*2 &amp;gt;= 0) {
                dp[i] += 2*dp[i - tmp*2];
                tmp++;
            }
        }
    }

    System.out.println(dp[dp.length-1]);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 막막할 수 있지만, 나올 수 있는 경우가 한정적이라는 걸 알고나면 굉장히 쉬워지는 문제다. 나올 수 있는 경우의 수는 아래의 5가지 경우가 끝이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;261&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baH1bw/btsN2RzSEXA/Zg2hVVwFJVDhUrzSZs19Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baH1bw/btsN2RzSEXA/Zg2hVVwFJVDhUrzSZs19Kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baH1bw/btsN2RzSEXA/Zg2hVVwFJVDhUrzSZs19Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaH1bw%2FbtsN2RzSEXA%2FZg2hVVwFJVDhUrzSZs19Kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;768&quot; height=&quot;261&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;261&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나눠보면 5가지가 나오고, 마지막 2개는 +2개씩 무한하게 늘어날 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;123번째로 dp배열 경우의 수를 채워주고, 45번째를 while문으로 더해주면 답이 금방 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+) 홀수인 경우는 구할 수 없기 때문에 0을 반환해주면 된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2631 줄 세우기 - 골드4&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2631&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/2631&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1747476697605&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    int cnt = Integer.parseInt(br.readLine());

    int[] arr=  new int[cnt];
    int[] dp = new int[cnt];

    for (int i = 0; i &amp;lt; cnt; i++) {
        arr[i] = Integer.parseInt(br.readLine());
    }

    int max = 0;
    for (int i =0; i&amp;lt;cnt;i++) {
        dp[i] = 0;
        for (int j =0; j&amp;lt;i; j++) {
            if (arr[j] &amp;lt; arr[i]) {
                if (dp[i] &amp;lt; dp[j] + 1) {
                    dp[i] = dp[j] + 1;
                    max = Math.max(dp[i], max);
                }
            }
        }
    }

    System.out.println(cnt - max);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(문제가 굉장히 귀여워서 마음에 든다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LIS라는 생각도 못했는데, LIS였다. 최장 증가 수열 개수를 찾아내서 고정시키고 나머지 애기들만 옮겨주면 최소 횟수로 옮길 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1516 게임 개발 - 골드3&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1516&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/1516&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1747480050957&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Node implements Comparable&amp;lt;Node&amp;gt; {
    int id;
    int cost;

    public Node(int id, int cost) {
        this.id = id;
        this.cost = cost;
    }

    @Override
    public int compareTo(Node o) {
        return Integer.compare(this.cost, o.cost);
    }
}

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int typeCnt = Integer.parseInt(br.readLine());
        List&amp;lt;Integer&amp;gt;[] relation = new ArrayList[typeCnt];
        List&amp;lt;Integer&amp;gt;[] dependsOn = new ArrayList[typeCnt];

        for (int i =0; i&amp;lt;typeCnt; i++) {
            relation[i] = new ArrayList&amp;lt;&amp;gt;();
            dependsOn[i] = new ArrayList&amp;lt;&amp;gt;();
        }

        boolean[] clear = new boolean[typeCnt];
        int[] times = new int[typeCnt];

        for (int i = 0; i &amp;lt; typeCnt; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine());

            //건축 시간 추가
            int time = Integer.parseInt(st.nextToken());
            times[i] = time;

            while (true) {
                int value = Integer.parseInt(st.nextToken());
                if (value == -1) {
                    break;
                }

                dependsOn[i].add(value-1);
                relation[value-1].add(i);
            }
        }

        int[] minTime = new int[typeCnt];
        Arrays.fill(minTime, Integer.MAX_VALUE);

        Queue&amp;lt;Node&amp;gt; queue = new PriorityQueue&amp;lt;&amp;gt;();
        for (int i = 0; i &amp;lt; typeCnt; i++) {
            if (dependsOn[i].isEmpty()) {
                queue.add(new Node(i, times[i]));
                minTime[i] = times[i];
            }
        }

        //dijkstra
        while (!queue.isEmpty()) {
            Node poll = queue.poll();

            if (poll.cost &amp;gt; minTime[poll.id]) {
                continue;
            }

            clear[poll.id] = true;

            for (int near: relation[poll.id]) {
                //다 안지어졌으면 ㄴㄴ
                boolean canStart = true;
                int maxTime = times[poll.id];
                for (int mom: dependsOn[near]) {
                    if (!clear[mom]) {
                        canStart = false;
                        break;
                    }
                    maxTime = Math.max(maxTime, minTime[mom]);
                }

                if (!canStart) {
                    continue;
                }

                if (minTime[near] &amp;gt; maxTime + times[near]) {
                    minTime[near] = maxTime + times[near];
                    queue.add(new Node(near, times[near]));
                }
            }
        }

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i &amp;lt; typeCnt; i++) {
            sb.append(minTime[i]).append(&quot;\n&quot;);
        }

        System.out.print(sb);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다익스트라로 풀릴 것 같아 해봤더니 풀렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 queue에 넣을 때, 의존하는 건물이 모두 세워진 건물만 queue에 넣어주도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+) 이 때 (의존하는 건물들 중 세워진 시간이 가장 큰 건물의 지은 시간 + 현재 건물이 지어지는 시간)이 현재 건물이 지어지는 시간이라는 걸 유의해야한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제는 위상정렬로도 풀 수 있다고 한다. 위상정렬이 기억나지 않아서.. 위상정렬로 푼 블로그를 첨부하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://steady-coding.tistory.com/86&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://steady-coding.tistory.com/86&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2342 Dance Dance Revolution - 골드3&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2342&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/2342&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1747488879255&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int[][][] dp = new int[100_001][5][5]; //넉넉하게 최댓 값으로 초기화
        StringTokenizer st = new StringTokenizer(br.readLine());

        int idx = 0;
        int last = 0; //마지막 발자취 저장

        //두 발 모두 0에서 시작
        for (int i = 0; i &amp;lt; 5; i++) {
            Arrays.fill(dp[0][i], 400001);
        }
        dp[0][0][0] = 0;

        while (true) {
            int value = Integer.parseInt(st.nextToken());
            if (value == 0) {
                break; //입력 끝
            }

            int current = ++idx;

            //초기화
            for (int i = 0; i &amp;lt; 5; i++) {
                Arrays.fill(dp[current][i], 400001);
            }

            //dp하기 - value를 밟기위한 모든 경우 계산
            for (int i = 0; i &amp;lt; 5; i++) {
                for (int j = 0; j &amp;lt; 5; j++) {
                    int strength = getStrength(j, value);
                    dp[current][i][value] = Math.min(dp[current][i][value], dp[current - 1][i][j] + strength);
                    dp[current][value][i] = Math.min(dp[current][value][i], dp[current - 1][j][i] + strength);
                }
            }

            last = value;
        }

        int min = Integer.MAX_VALUE;
        for (int i = 0; i &amp;lt; 5; i++) {
            min = Math.min(min, dp[idx][i][last]);
            min = Math.min(min, dp[idx][last][i]);
        }

        System.out.println(min);
    }

    private static int getStrength(int a, int b) {
        if (a == b) {
            return 1;
        }

        if (a == 0) {
            return 2;
        }

        if (Math.abs(a - b) == 2) {
            return 4;
        }

        return 3;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dp는 3차원 배열을 사용해줬다. 각각은 다음을 의미한다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[ㅇ][][]:&amp;nbsp; 밟아야하는 발판의 인덱스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[][ㅇ][]: 왼발 위치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[][][ㅇ]: 오른발 위치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 step을 밟을 때 초기화해주는 로직은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) 4를 밟아야하는 상황&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;오른발로 4를 밟기: dp[current][i][4] = Math.min(&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;dp[current][i][4], dp[current-1][i][j] + 발옮기기비용)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;왼발로 4를 밟기:&amp;nbsp; dp[current][4][i] = Math.min(dp[current][4][i], dp[current-1][j][i] + 발옮기기비용)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 i와 j는 0~4까지 이중루프로 돈다. 그냥 모든 발 위치인 경우를 다 고려해서 dp배열을 갱신한다고 보면된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발을 옮기는 비용은 메서드로 따로 빼줬다. 문제에 명시되어있는 그대로 구현해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/백준</category>
      <author>fladi</author>
      <guid isPermaLink="true">https://fladi.tistory.com/454</guid>
      <comments>https://fladi.tistory.com/454#entry454comment</comments>
      <pubDate>Sat, 17 May 2025 18:01:07 +0900</pubDate>
    </item>
    <item>
      <title>[JAVA] 백준 슬라이딩 윈도우 부수기</title>
      <link>https://fladi.tistory.com/453</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 후순위로 밀리던 슬라이딩 윈도우 문제를 풀어보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2559 수열 - 실버3&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2559&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/2559&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1746609788313&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    StringTokenizer st = new StringTokenizer(br.readLine());

    int n = Integer.parseInt(st.nextToken());
    int k = Integer.parseInt(st.nextToken());

    st = new StringTokenizer(br.readLine());
    int[] arr = new int[n];
    int maxSum = -10_000_001;
    int sum= 0;
    int tmp = k - 1;
    for (int i =0; i&amp;lt;n; i++) {
        arr[i] = Integer.parseInt(st.nextToken());

        sum += arr[i];

        if (i &amp;lt; tmp) {
            continue;
        }

        if (i &amp;gt;= k) {
            sum -= arr[i - k];
        }

        if (sum &amp;gt; maxSum) {
            maxSum = sum;
        }
    }

    System.out.println(maxSum);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 앞뒤 숫자를 큐스택처럼 빼는 문제였다. 어렵지 않다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #585f69; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;21921 블로그 - 실버3&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/21921&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/21921&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1746610531277&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    StringTokenizer st = new StringTokenizer(br.readLine());

    int n = Integer.parseInt(st.nextToken());
    int x = Integer.parseInt(st.nextToken());

    st = new StringTokenizer(br.readLine());
    int[] visitorCnt = new int[n];

    int tmp = x - 1;
    int cnt = 0;
    int maxSum = 0;
    int currentSum  =0;
    
    for (int i =0; i&amp;lt;n; i++) {
        visitorCnt[i] = Integer.parseInt(st.nextToken());
        currentSum += visitorCnt[i];

        if (i &amp;lt; tmp)
            continue;

        if (i &amp;gt; tmp) {
            currentSum -= visitorCnt[i-x];
        }

        //갱신작업
        if (currentSum == maxSum) {
            cnt ++;
        }

        if (currentSum &amp;gt; maxSum) {
            maxSum = currentSum;
            cnt = 1;
        }
    }

    //출력
    if (maxSum == 0) {
        System.out.println(&quot;SAD&quot;);
    } else {
        System.out.println(maxSum + &quot;\n&quot; + cnt);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;윗 문제와 굉장히 유사하다. 어렵지 않다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #585f69; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;12891 DNA 비밀번호 - 실버2&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/12891&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/12891&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1746628010101&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Main {
    static int[] currentAlpha;
    static int[] requiredAlpha;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        requiredAlpha = new int[4];
        currentAlpha = new int[4];
        
        int s = Integer.parseInt(st.nextToken());
        int p = Integer.parseInt(st.nextToken());
        String input = br.readLine();
        
        st = new StringTokenizer(br.readLine());
        for (int i = 0; i &amp;lt; 4; i++) {
            requiredAlpha[i] = Integer.parseInt(st.nextToken());
        }

        for (int i = 0; i &amp;lt; p; i++) {
            increaseCurrentAlpha(input.charAt(i));
        }

        int result = 0;
        if (check()) {
            result++;
        }

        for (int i = 1; i &amp;lt; input.length() + 1 - p; i++) {
            decreaseCurrentAlpha(input.charAt(i - 1));
            increaseCurrentAlpha(input.charAt(i + p - 1));
            
            if (check()) {
                result++;
            }
        }

        System.out.println(result);
    }

    private static void decreaseCurrentAlpha(char c) {
        if (c == 'A') {
            currentAlpha[0]--;
        } else if (c == 'C') {
            currentAlpha[1]--;
        } else if (c == 'G') {
            currentAlpha[2]--;
        } else if (c == 'T') {
            currentAlpha[3]--;
        }
    }

    private static boolean check() {
        boolean pass;
        pass = true;
        for (int j = 0; j &amp;lt; 4; j++) {
            if (requiredAlpha[j] &amp;gt; currentAlpha[j]) {
                pass = false;
                break;
            }
        }
        return pass;
    }

    private static void increaseCurrentAlpha(char c) {
        if (c == 'A') {
            currentAlpha[0]++;
        } else if (c == 'C') {
            currentAlpha[1]++;
        } else if (c == 'G') {
            currentAlpha[2]++;
        } else if (c == 'T') {
            currentAlpha[3]++;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot; &lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;부분문자열이 등장하는 위치가 다르다면 부분문자열이 같다고 하더라도 다른 문자열로 취급한다.&quot;라는 조건때문에 쉬워진 문제인 것 같다. &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; letter-spacing: 0px;&quot;&gt;알파벳이 26개밖에 안되기 때문에 배열로 최적화한 풀이들이 많아서 재미있었다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #585f69; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;2531 회전 초밥 - 실버1&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2531&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/2531&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1746632025863&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Main {
    static int max;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        int plateCnt = Integer.parseInt(st.nextToken());
        int typeCnt = Integer.parseInt(st.nextToken());
        int k = Integer.parseInt(st.nextToken());
        int coupon = Integer.parseInt(st.nextToken());

        int[] plate = new int[plateCnt+k-1]; //k-1개만큼 배열 늘리기
        int[] current = new int[typeCnt+1]; //접시타입별 개수 저장용
        int currentTypeCnt = 0;
        max = 0;

        for (int p =0; p&amp;lt;plate.length; p++) {
            //입력받기
            if (p &amp;lt; plateCnt) {
                plate[p] = Integer.parseInt(br.readLine());
            }

            int type = plate[p];
            
            //현재값 갱신
            current[type] ++;
            if (current[type] == 1) {
                currentTypeCnt++;
            }

            //사이클 갱신용
            if (p &amp;lt; k-1) {
                plate[plateCnt + p] = type;
                continue;
            }

            //앞에꺼 빼주기
            if (p &amp;gt;= k) {
                int prevType = plate[p - k];
                current[prevType] --;
                if (current[prevType] == 0) {
                    currentTypeCnt--;
                }
            }

            //max값 갱신
            updateMax(currentTypeCnt, current, coupon);
        }

        System.out.println(max);
    }

    private static void updateMax(int currentTypeCnt, int[] current, int coupon) {
        int tmpCurrentTypeCnt = currentTypeCnt;

        //쿠폰고려해서 typecnt 갱신
        if (current[coupon] ==0) {
            tmpCurrentTypeCnt++;
        }

        if (tmpCurrentTypeCnt &amp;gt; max) {
            max = tmpCurrentTypeCnt;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이클만 고려하면 어렵지 않은 문제다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이딩윈도우 초기값을 채우는 과정과 사이클을 위한 배열 추가값을 더하는 과정을 한 번에 진행하느라 코드가 깔끔하지 못한 것 같다. 나중에 나도 못알아볼 것 같아서 주석이라도 열심히 달았다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #585f69; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;15565 귀여운 라이언 - 실버1&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/15565&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/15565&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1746687557717&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    StringTokenizer st = new StringTokenizer(br.readLine());

    int n = Integer.parseInt(st.nextToken());
    int k = Integer.parseInt(st.nextToken());

    //라이언 인형 위치 저장용
    int[] lion = new int[n]; //최대 라이언인형 개수는 n개 -&amp;gt; n개로 넉넉하게 잡음
    int lionIdx=  0; //저장된 라이언 인형 개수

    st = new StringTokenizer(br.readLine());
    for (int i = 0; i &amp;lt; n; i++) {
        int doll = Integer.parseInt(st.nextToken());
        if (doll == 1) {
            lion[lionIdx++] = i;
        }
    }

    //전체 라이언인형 개수가 k보다 작으면 -1 출력 후 종료
    if (lionIdx &amp;lt; k) {
        System.out.println(-1);
        return;
    }

    //lion인형 k개를 유지하면서 범위크기 비교
    int min= Integer.MAX_VALUE;
    int end = lionIdx - k + 1;
    for (int i = 0; i&amp;lt; end; i++) {
        int nextIdx = i + k - 1;
        int tmp = lion[nextIdx] - lion[i] + 1;
        if (tmp &amp;lt; min) {
            min = tmp;
        }
    }

    System.out.println(min);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 전체 배열에 대해서 start와 end를 조절하면서 라이언 인형 개수를 맞추고자 했는데, 조금만 생각을 틀면 훨씬 빨리 구할 수 있는 방법이 있었다. 내가 생각한 풀이는 아니고 인터넷 풀이를 참고해서 풀었다. (참고한 블로그: &lt;a href=&quot;https://chan-it-note.tistory.com/100&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://chan-it-note.tistory.com/100&lt;/a&gt;)&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;라이언 인형 위치만 배열에 저장한다&lt;/li&gt;
&lt;li&gt;라이언 인형 배열에서 투포인터를 사용하여 라이언인형 K개일 때 최소 범위사이즈를 구한다&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1593 문자 해독&lt;span&gt;&amp;nbsp;&lt;/span&gt;- 골드5&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1593&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/1593&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1747054620179&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    StringTokenizer st = new StringTokenizer(br.readLine());

    int wordLength = Integer.parseInt(st.nextToken());
    int totalLength = Integer.parseInt(st.nextToken());

    String word = br.readLine();
    String totalWord = br.readLine();

    int[] wordCnt = new int[58];
    for (int i = 0; i &amp;lt; wordLength; i++) {
        wordCnt[word.charAt(i) - 'A']++;
    }

    //초기값 넣기
    int[] currentCnt = new int[58];
    for (int i = 0; i &amp;lt; wordLength; i++) {
        currentCnt[totalWord.charAt(i) - 'A']++;
    }

    int result = 0;
    if (Arrays.equals(currentCnt, wordCnt)) {
        result++;
    }
    
    //슬라이딩 윈도우 수행
    int endWindow = totalLength - wordLength + 1;
    for (int i = 1; i &amp;lt; endWindow; i++) {
        currentCnt[totalWord.charAt(i-1) - 'A']--;
        currentCnt[totalWord.charAt(i+wordLength - 1) - 'A']++;

        if (Arrays.equals(currentCnt, wordCnt)) {
            result++;
        }
    }

    System.out.println(result);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이딩 윈도우 방법으로 하나씩 비교하면 되는 문제다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항상 배열 값 하나하나 비교하는 방식을 사용했는데, 다른 풀이를 보다가 Arrays.equals로 비교가능하다는 걸 알게되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2096 내려가기&lt;span&gt;&amp;nbsp;&lt;/span&gt;- 골드5&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2096&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/2096&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1747055641094&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    int n = Integer.parseInt(br.readLine());

    int[] minScore = new int[3];
    int[] maxScore = new int[3];

    for (int i = 0; i &amp;lt; n; i++) {
        StringTokenizer st = new StringTokenizer(br.readLine());

        int a = Integer.parseInt(st.nextToken());
        int b = Integer.parseInt(st.nextToken());
        int c = Integer.parseInt(st.nextToken());

        int[] tmpMinScore = new int[3];
        tmpMinScore[0] = getMin(minScore[0], minScore[1]) + a;
        tmpMinScore[1] = getMin(minScore[0], minScore[1], minScore[2]) + b;
        tmpMinScore[2] = getMin(minScore[1], minScore[2]) + c;

        minScore = tmpMinScore;

        int[] tmpMaxScore = new int[3];
        tmpMaxScore[0] = getMax(maxScore[0], maxScore[1]) + a;
        tmpMaxScore[1] = getMax(maxScore[0], maxScore[1], maxScore[2]) + b;
        tmpMaxScore[2] = getMax(maxScore[1], maxScore[2]) + c;

        maxScore = tmpMaxScore;
    }

    int realMax = 0;
    int realMin = Integer.MAX_VALUE;

    for (int i = 0; i &amp;lt; 3; i++) {
        realMax = Math.max(realMax, maxScore[i]);
        realMin = Math.min(realMin, minScore[i]);
    }

    System.out.println(realMax + &quot; &quot; + realMin);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이딩 윈도우보단 dp로 보는게 맞는 문제인 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3078 좋은 친구 - 골드4&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/3078&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/3078&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1747058834630&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    StringTokenizer st = new StringTokenizer(br.readLine());

    //좋은 친구 쌍 = 결과값
    long goodFriendPairCnt = 0L;

    int n = Integer.parseInt(st.nextToken());
    int windowSize = Integer.parseInt(st.nextToken()) + 1; //넘으면 친구가 아니다 = 같으면 친구다

    //이름 입력 받기
    int[] nameLen = new int[n];
    for (int i = 0; i &amp;lt; n; i++) {
        nameLen[i] = br.readLine().length();
    }

    //현재까지 이름 길이 개수 저장 - 시간줄이기 용
    int[] currentCnt = new int[21];
    for (int i=0; i&amp;lt;windowSize; i++) {
        currentCnt[nameLen[i]] ++;
    }

    //초기 비교, 좋은 친구 쌍 갱신
    for (int i =0; i&amp;lt;21; i++) {
        if (currentCnt[i] &amp;gt; 1) {
            goodFriendPairCnt += ((long)(currentCnt[i]) * (currentCnt[i] - 1) / 2);
        }
    }

    //슬라이딩 윈도우 -&amp;gt; 추가된 애랑 앞에 친구들 비교만 하면 됨
    for (int i = windowSize; i&amp;lt; n; i++) {
        currentCnt[nameLen[i - windowSize]]--;
        currentCnt[nameLen[i]]++;
        
        goodFriendPairCnt += (currentCnt[nameLen[i]] - 1);
    }

    System.out.println(goodFriendPairCnt);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름 길이 개수를 저장하지 않아서 시간초과가 발생했고, 친구 쌍 개수가 300,000 * 300,000 = 90,000,000,000 정도로 int 최대값을 넘어가는 경우를 고려하지 않아서 한 번 틀렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이딩 윈도우 과정에서 빠르게 비교할 수 있도록 개수값을 캐싱해두는 것과, 숫자범위만 잘 처리해주면 어렵지는 않은 문제다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;15961 회전 초밥 - 골드4&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/15961&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/15961&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1747061115510&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    StringTokenizer st = new StringTokenizer(br.readLine());

    int n = Integer.parseInt(st.nextToken());
    int typeCnt = Integer.parseInt(st.nextToken());
    int winSize = Integer.parseInt(st.nextToken());
    int couponNum = Integer.parseInt(st.nextToken());

    int[] tmpArr = new int[winSize]; //i % winSize 값을 임시로 채울 배열
    int[] frontArr = new int[winSize]; //0 ~ winSize 값을 채우는 배열
    int[] currentCnt = new int[typeCnt + 1]; //초밥 타입별 개수 저장할 배열

    int currentTypeCnt = 0; //현재까지 타입개수
    int maxTypeCnt = 0; //결과

    //windowSize까지 채워주기
    for (int i = 0; i &amp;lt; winSize; i++) {
        int num = Integer.parseInt(br.readLine());
        frontArr[i] = num;
        tmpArr[i] = num;

        currentCnt[num]++;
        if (currentCnt[num] == 1) {
            currentTypeCnt++;
        }
    }

    int plusFood = (currentCnt[couponNum] == 0) ? 1 : 0; //쿠폰 초밥 추가 가능여부
    if (currentTypeCnt + plusFood &amp;gt; maxTypeCnt) {
        maxTypeCnt = currentTypeCnt + plusFood;
    }

    //슬라이딩 윈도우
    for (int i = winSize; i &amp;lt; n; i++) {
        int num = Integer.parseInt(br.readLine());

        //현재 값 추가
        currentCnt[num]++;
        if (currentCnt[num] == 1) {
            currentTypeCnt++;
        }

        //이전값 계산 -&amp;gt; tmpArr 에 저장해둔 값 사용
        int prevNum = tmpArr[(i - winSize) % winSize];
        currentCnt[prevNum]--;
        if (currentCnt[prevNum] == 0) {
            currentTypeCnt--;
        }

        plusFood = (currentCnt[couponNum] == 0) ? 1 : 0; //쿠폰 초밥 추가 가능여부
        if (currentTypeCnt + plusFood &amp;gt; maxTypeCnt) {
            maxTypeCnt = currentTypeCnt + plusFood;
        }

        tmpArr[i % winSize] = num;
    }

    //마지막 값들 처리
    for (int i = 0; i &amp;lt; winSize - 1; i++) {
        int prevNum = tmpArr[(n - winSize + i) % winSize];
        currentCnt[prevNum]--;
        if (currentCnt[prevNum] == 0) {
            currentTypeCnt--;
        }

        //미리 저장해둔 값 사용
        currentCnt[frontArr[i]]++;
        if (currentCnt[frontArr[i]] == 1) {
            currentTypeCnt++;
        }

        plusFood = (currentCnt[couponNum] == 0) ? 1 : 0; //쿠폰 초밥 추가 가능여부
        if (currentTypeCnt + plusFood &amp;gt; maxTypeCnt) {
            maxTypeCnt = currentTypeCnt + plusFood;
        }
    }

    System.out.println(maxTypeCnt);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실버문제와 거의 동일한 문제다. 아마 입력값 범위만 달라진 것 같다.(시간초과가 날까봐 조마조마했다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력으로 들어오는 300만개 데이터를 다 저장할 수 없지만, 슬라이딩 윈도우를 위한 이전 값은 저장이 필요했다. 그래서 k크기만큼만 저장하고, 나머지 연산자를 열심히 사용해줬다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 총 세 부분으로 나눠서 문제를 풀었다.&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;0 ~ k-1까지 처리 (초기값 저장 위함)&lt;/li&gt;
&lt;li&gt;k ~ n 까지 처리 (슬라이딩 윈도우로 갱신)&lt;/li&gt;
&lt;li&gt;0 ~ k-1까지 처리 (순환 처리)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장소는 이전 값을 저장할 tmpArr 과 0~k-1까지 값을 저장할 frontArr을 사용하였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이딩 윈도우 과정에서 윈도우 사이즈를 벗어나 빼줘야하는 값은 tmpArr에서 빼왔고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순환 처리 과정에서는 frontArr 값을 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;23295 스터디 시간 정하기 1 - 골드3&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/23295&quot;&gt;https://www.acmicpc.net/problem/23295&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1747069367526&quot; class=&quot;arduino&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    StringTokenizer st = new StringTokenizer(br.readLine());

    int manCnt = Integer.parseInt(st.nextToken());
    int time = Integer.parseInt(st.nextToken());

    int[] arr = new int[100_001]; //들어오고 나가는 사람 기록용(들어오면 1, 나가면 -1)
    int[] sum = new int[100_001]; //만족도 캐싱용

    //입력받기
    for (int i = 0; i &amp;lt; manCnt; i++) {
        int cnt = Integer.parseInt(br.readLine());

        for (int j = 0; j &amp;lt; cnt; j++) {
            st = new StringTokenizer(br.readLine());
            int start = Integer.parseInt(st.nextToken());
            int end = Integer.parseInt(st.nextToken());

            arr[start]++;
            arr[end]--;
        }
    }

    int startTime = 0;
    int endTime = time;
    long maxScore = 0;
    long currentScore = 0;

    //초기값 계산
    int tmpScore = 0; //현재 나가거나 들어온 사람 값
    for (int i = 0; i &amp;lt; time; i++) {
        tmpScore += arr[i];
        sum[i] = tmpScore; //캐싱
        currentScore += tmpScore;
    }

    if (currentScore &amp;gt; maxScore) {
        maxScore = currentScore;
    }

    //슬라이딩 윈도우
    for (int i = time; i &amp;lt; arr.length; i++) {
        currentScore -= sum[i - time]; //캐싱한 값 빼주기

        tmpScore += arr[i];
        sum[i] = tmpScore;
        currentScore += tmpScore;

        if (currentScore &amp;gt; maxScore) {
            maxScore = currentScore;

            startTime = i - time + 1;
            endTime = i + 1; //시간범위는 1 더해줘야함
        }
    }

    System.out.println(startTime + &quot; &quot; + endTime);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감도 안잡혀서 풀이를 찾아봤다. 들어오고 나가는 사람의 수를 체크하고, 슬라이딩 윈도우 방식으로 최댓값을 구해주면 어렵지 않게 구할 수 있었다. 아이디어를 찾는 게 가장 중요했다. (+ 범위값을 잘 봐야한다. long으로 안하면 틀린다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;5444 시리얼 넘버 - 골드1&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/5444&quot;&gt;https://www.acmicpc.net/problem/5444&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1747122335857&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

    int tcCnt = Integer.parseInt(br.readLine());
    for (int t = 0; t &amp;lt; tcCnt; t++) {
        StringTokenizer st = new StringTokenizer(br.readLine());

        int n = Integer.parseInt(st.nextToken());
        int m = Integer.parseInt(st.nextToken());

        int[][] dp = new int[2][m];
        Arrays.fill(dp[0], -1);
        Arrays.fill(dp[1], -1);
        dp[1][0] = 0;

        st = new StringTokenizer(br.readLine());
        for (int i = 0; i &amp;lt; n; i++) {
            int serialNum = Integer.parseInt(st.nextToken());
            int current = i % 2;
            int prev = (i + 1) % 2;

            for (int j = 0; j &amp;lt; m; j++) {
                if (dp[prev][j] == -1) {
                    continue;
                }

                dp[current][j] = Math.max(dp[current][j], dp[prev][j]);

                // 선택하는 경우
                int next = (j + serialNum) % m;
                if (dp[current][next] == -1 || dp[current][next] &amp;lt; dp[prev][j] + 1) {
                    dp[current][next] = dp[prev][j] + 1;
                }
            }
        }

        System.out.println(dp[(n - 1) % 2][0]);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;문제유형은 슬라이딩 윈도우라고 되어있는데, 그냥 순환하는 dp문제였다. 아이디어는 떠올렸는데 관련 문제를 푼 경험이 없어서 애를 먹었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;dp배열 크기를 줄이기 위해 %m을 사용하였고, 이전 데이터를 누적하기 위해 2개의 행도 사용해줬다. 2개의 행도 %2로 굉장히 아껴썼다. 신박한 유형이라 굉장히 재미있었다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며..&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이딩 윈도우의 느낌은 알았는데, 아직 범위를 고려해서 깔끔하게 코드를 짜는 숙련도는 부족한 것 같다. 한 10개는 더 풀어봐야할 듯 하다.&lt;/p&gt;</description>
      <category>알고리즘/백준</category>
      <author>fladi</author>
      <guid isPermaLink="true">https://fladi.tistory.com/453</guid>
      <comments>https://fladi.tistory.com/453#entry453comment</comments>
      <pubDate>Mon, 12 May 2025 22:14:27 +0900</pubDate>
    </item>
    <item>
      <title>[MySQL] Programmers SQL 고득점 Kit 풀기 - GROUP BY(2)</title>
      <link>https://fladi.tistory.com/452</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;이전 글&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://fladi.tistory.com/451&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://fladi.tistory.com/451&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL 고득점 Kit 마지막 글이 될 것 같다. 더 풀고싶은데 조금 아쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GROUP BY도 계속 풀다보니 익숙해지는 것 같다. 감을 잃지 않도록 계속 복습해야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;13. 년, 월, 성별 별 상품 구매 회원 수 구하기 - 레벨4&lt;/h2&gt;
&lt;pre id=&quot;code_1739809607917&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT YEAR(O.SALES_DATE) AS YEAR, 
    MONTH(O.SALES_DATE) AS MONTH,
    U.GENDER AS GENDER,
    COUNT(DISTINCT U.USER_ID) AS USERS
FROM ONLINE_SALE O
JOIN USER_INFO U
ON O.USER_ID = U.USER_ID
WHERE U.GENDER IS NOT NULL
GROUP BY YEAR(O.SALES_DATE), MONTH(O.SALES_DATE), U.GENDER
ORDER BY YEAR, MONTH, GENDER&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;생각없이 풀다가 한 번 틀렸다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;JOIN을 해준다&lt;/li&gt;
&lt;li&gt;GENDER가 NULL인 데이터는 포함하지 않는다&lt;/li&gt;
&lt;li&gt;GROUP BY를 조건대로 수행해준다&lt;/li&gt;
&lt;li&gt;COUNT에서는 회원 수를 집계해야하는 문제이기 때문에 DISTINCT를 사용해줘야한다. 같은 년,월에 같은 사람이 2번 이상 구매할 수 있기 때문이다. 같은 년, 월일 때 한 사람은 한 번만 집계되어야한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;14. 입양 시각 구하기(1) - 레벨2&lt;/h2&gt;
&lt;pre id=&quot;code_1739809987370&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT HOUR(`DATETIME`) AS HOUR, COUNT(*) AS COUNT
FROM ANIMAL_OUTS
WHERE HOUR(`DATETIME`) BETWEEN 9 AND 20
GROUP BY HOUR(`DATETIME`)
ORDER BY HOUR&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;어렵지 않다&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;15. 입양 시각 구하기(2) - 레벨4 ✨✨&lt;/h2&gt;
&lt;pre id=&quot;code_1739810494666&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SET @hour := -1;

WITH HOURTABLE AS (
    SELECT (@hour := @hour + 1) AS HOUR
    FROM ANIMAL_OUTS
    WHERE @hour &amp;lt; 23
),

HOURCOUNT AS (
    SELECT HOUR(DATETIME) AS HOUR, COUNT(*) AS COUNT
    FROM ANIMAL_OUTS
    GROUP BY HOUR(DATETIME)
) 

SELECT HT.HOUR, IFNULL(HC.COUNT, 0) AS COUNT
FROM HOURTABLE HT
LEFT JOIN HOURCOUNT HC
ON HT.HOUR = HC.HOUR
ORDER BY HT.HOUR&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;INSERT문 없이 도대체 어떻게 풀어야할지 감도 안잡혔다. 구글에 검색해보니 ROW_NUMBER()를 이용해서 어거지로 0~23을 만들어내신 분도 있었고, SET을 사용하는 사람도 있었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(ROW_NUMBER()를 사용하신 분은 정말 존경스럽다. 자신이 아는 선에서 정답을 내놓긴 했으니말이다. 이 분의 블로그링크이다 &lt;a href=&quot;https://hellobrocolli.tistory.com/25&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hellobrocolli.tistory.com/25&lt;/a&gt;)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;완전히 새로운 문법이 나왔으니 정리해보겠다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;SET으로 새로운 사용자 정의 변수 @hour를 정의한다. := 는 값을 할당하는 연산자이다&lt;/li&gt;
&lt;li&gt;변수 @hour를 -1부터 1씩 증가시키면서 0 ~ 23이 담긴 테이블 HOURTABLE을 만든다&lt;/li&gt;
&lt;li&gt;HOUR(DATETIME)을 기준으로 GROUP BY를 수행하여 개수를 센 HOURCOUNT를 만들어준다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;HOURTABLE과 HOURCOUNT를 JOIN해주고, HOURCOUNT에 없는 경우(NULL인 경우) 0으로 COUNT를 지정해서 출력한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;lt;다른 방법 - SET사용하지 않고 재귀 CTE 사용&amp;gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1739811881707&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;WITH RECURSIVE HOURS AS (
    SELECT 0 AS HOUR
    UNION ALL
    SELECT HOUR + 1
    FROM HOURS
    WHERE HOUR &amp;lt; 23
)
SELECT H.HOUR, IFNULL(COUNT(A.DATETIME), 0) AS COUNT
FROM HOURS H
LEFT JOIN ANIMAL_OUTS A ON H.HOUR = HOUR(A.DATETIME)
GROUP BY H.HOUR
ORDER BY H.HOUR;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;SET을 사용하지 않는 방법도 있다. RECURSIVE를 사용하는 방법이다. 재귀 CTE라고 불리는 방법이라고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;초기행으로 0을 생성&lt;/li&gt;
&lt;li&gt;UNION ALL을 통해 다음 값 생성&lt;/li&gt;
&lt;li&gt;WHERE 조건으로 23까지만 생성되도록 제한&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;두 방법 모두 기억해뒀다가 SQL 코테 때 기억나는 친구를 사용해야겠다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;16. 가격대 별 상품 개수 구하기&lt;/h2&gt;
&lt;pre id=&quot;code_1739813451406&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT FLOOR(PRICE/10000)*10000 AS PRICE_GROUP, COUNT(*) AS PRODUCTS
FROM PRODUCT
GROUP BY FLOOR(PRICE/10000)
ORDER BY PRICE_GROUP&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;만원대로 나누는 방식으로 그룹을 정해서 풀었다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;17. 언어별 개발자 분류하기&lt;/h2&gt;
&lt;pre id=&quot;code_1739878095553&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;WITH FE_SKILL AS (
    SELECT BIT_OR(CODE) AS CODE
    FROM SKILLCODES
    WHERE CATEGORY = 'Front End'
),

D_WITH_GRADE AS (
    SELECT ID, EMAIL, CASE
        WHEN SKILL_CODE &amp;amp; (SELECT CODE FROM SKILLCODES WHERE NAME='Python') &amp;gt; 0 AND SKILL_CODE &amp;amp; (SELECT CODE FROM FE_SKILL) &amp;gt; 0 THEN 'A'
        WHEN SKILL_CODE &amp;amp; (SELECT CODE FROM SKILLCODES WHERE NAME='C#')  &amp;gt; 0 THEN 'B'
        WHEN SKILL_CODE &amp;amp; (SELECT CODE FROM FE_SKILL) &amp;gt; 0 THEN 'C'
        ELSE NULL
    END AS GRADE
    FROM DEVELOPERS
)

SELECT GRADE, ID, EMAIL
FROM D_WITH_GRADE
WHERE GRADE IS NOT NULL
ORDER BY GRADE, ID&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;꼼수 부린다고 code값을 하드코딩했더니 틀렸다. 직접 Python과 FrontendSkill인 코드를 찾아야했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이 코드처럼 SELECT 문에 SELECT문을 넣으면 레코드 각각마다 조회가 일어난다. (물론 FE_SKILL 테이블에는 레코드가 하나만 들어있어서 크게 성능차이는 나지 않을 수 있따) SELECT 안에 SELECT문을 넣지 않기 위해서는 CROSS JOIN을 사용할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1739939802057&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 먼저 필요한 모든 스킬 코드를 한번에 조회
WITH BASE_SKILLS AS (
    SELECT 
        MAX(CASE WHEN NAME = 'Python' THEN CODE END) AS PYTHON_CODE,
        MAX(CASE WHEN NAME = 'C#' THEN CODE END) AS CSHARP_CODE
    FROM SKILLCODES
),

FRONTEND_SKILLS AS (
    SELECT BIT_OR(CODE) AS FE_CODE
    FROM SKILLCODES
    WHERE CATEGORY = 'Front End'
),

GRADED_DEVELOPERS AS (
    SELECT 
        D.ID,
        D.EMAIL,
        CASE
            WHEN D.SKILL_CODE &amp;amp; BS.PYTHON_CODE &amp;gt; 0 
                 AND D.SKILL_CODE &amp;amp; FS.FE_CODE &amp;gt; 0 
                 THEN 'A'
            WHEN D.SKILL_CODE &amp;amp; BS.CSHARP_CODE &amp;gt; 0 
                 THEN 'B'
            WHEN D.SKILL_CODE &amp;amp; FS.FE_CODE &amp;gt; 0 
                 THEN 'C'
        END AS GRADE
    FROM DEVELOPERS D
    CROSS JOIN BASE_SKILLS BS
    CROSS JOIN FRONTEND_SKILLS FS
    # WHERE D.SKILL_CODE &amp;amp; (BS.PYTHON_CODE | BS.CSHARP_CODE | FS.FE_CODE) &amp;gt; 0
)

SELECT 
    GRADE,
    ID,
    EMAIL
FROM GRADED_DEVELOPERS
WHERE GRADE IS NOT NULL
ORDER BY 
    GRADE ASC,
    ID ASC;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Python과 C# 스킬을 attribute로 가지는 CTE를 만들어준다. 이 때 레코드는 MAX를 사용하여 한 건만 저장되도록 한다&lt;/li&gt;
&lt;li&gt;Frontend 스킬을 attribute로 가지는 CTE도 만들어준다. 여기서도 BIT_OR을 사용하여 한 건만 저장되도록 한다(Cross join을 사용하기 위함)&lt;/li&gt;
&lt;li&gt;등급을 매길 GRADED_DEVELOPERS CTE를 만든다. CROSS JOIN을 사용하여 테이블에 Python, Frontend 등의 속성을 추가시킨 후 등급을 매긴다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;(이렇게 하면 DEVELOPERS 테이블에 해당하는 각 레코드에 PYTHON_CODE, CSHARP_CODE, FE_CODE 3개의 attribute가 추가된다. BASE_SKILL과 FRONTEND_SKILLS 테이블에는 레코드가 하나만 있기 때문에 결과 레코드 수는 DEVELOPERS 테이블 레코드수와 동일하다.)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;GRADE가 NULL이 아닌 레코드를 조회하여 ORDER BY를 수행해준다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 CROSS JOIN을 처음 써봤는데 좀 참신했다. 기억해둬야겠다. .&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;18. 조건에 맞는 사원 정보 조회하기&lt;/h2&gt;
&lt;pre id=&quot;code_1739951454638&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;WITH TMP AS (
    SELECT SUM(HG.SCORE) AS SCORE, 
        HE.EMP_NO, 
        HE.EMP_NAME, 
        HE.POSITION, 
        HE.EMAIL
    FROM HR_GRADE HG
    JOIN HR_EMPLOYEES HE
    ON HG.EMP_NO = HE.EMP_NO
    WHERE HG.YEAR = '2022'
    GROUP BY HE.EMP_NO
),

TMP2 AS (
    SELECT SCORE, 
        EMP_NO, 
        EMP_NAME, 
        POSITION, 
        EMAIL,
        RANK() OVER (ORDER BY SCORE DESC) AS RRANK
    FROM TMP
)

SELECT SCORE, 
    EMP_NO, 
    EMP_NAME, 
    POSITION, 
    EMAIL
FROM TMP2 AS A
WHERE RRANK = 1;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;생각보다 복잡해서 차근차근 하나씩 풀어냈다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;TMP CTE에서 EMP_NO를 기준으로 2022년 데이터를 GROUP BY로 묶어준다. 2022년 합계 SCORE를 구할 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;TMP2 CTE에서 SCORE가 높은 순으로 RANK를 매겨준다.&amp;nbsp;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이 때 SCORE가 가장 높은 레코드(RRANK가 1인 레코드)는 여러 개가 나올 수 있다&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;가장 높은 SCORE를 가지는(RANK가 1인) 레코드를 출력한다&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;만약 가장 높은 SCORE를 가지는 사원 1명만 출력하고 싶다면 LIMIT 1을 사용하거나, ROW_NUMBER를 사용하기나, FIRST_VALUE를 사용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lt;다른 쉬운 풀이&amp;gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739952080497&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT SUM(SCORE) AS SCORE, G.EMP_NO, E.EMP_NAME, E.POSITION, E.EMAIL
FROM HR_EMPLOYEES E
    INNER JOIN HR_GRADE G ON E.EMP_NO = G.EMP_NO
WHERE G.YEAR = '2022'
GROUP BY EMP_NO
ORDER BY 1 DESC
LIMIT 1;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그냥 ORDER BY를 쓰는 아주 간단한 풀이도 있다. 나는 RANK부여하고 CTE를 2개나 만들면서 복잡하게 풀었는데, 단순하게 생각하면 굉장히 쉬운 문제인 것 같다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;19. 연간 평가점수에 해당하는 평가 등급 및 성과금 조회하기&lt;/h2&gt;
&lt;pre id=&quot;code_1739955331580&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;WITH EMP_SUM AS (
    SELECT HE.EMP_NO, HE.EMP_NAME, HE.SAL, AVG(SCORE) AS SUM_SCORE
    FROM HR_EMPLOYEES HE
    JOIN HR_GRADE HG
    ON HE.EMP_NO = HG.EMP_NO
    GROUP BY EMP_NO
)

SELECT EMP_NO, EMP_NAME, 
    CASE 
        WHEN SUM_SCORE &amp;gt;= 96 THEN 'S'
        WHEN SUM_SCORE &amp;gt;= 90 THEN 'A'
        WHEN SUM_SCORE &amp;gt;= 80 THEN 'B'
        ELSE 'C'
    END AS GRADE, 
    CASE 
        WHEN SUM_SCORE &amp;gt;= 96 THEN SAL * 20 / 100
        WHEN SUM_SCORE &amp;gt;= 90 THEN SAL * 15 / 100
        WHEN SUM_SCORE &amp;gt;= 80 THEN SAL * 10 / 100
        ELSE 0
    END AS BONUS
FROM EMP_SUM
ORDER BY EMP_NO ASC&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;차근차근하면 어렵지 않은 문제다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;GROUP BY를 모두 수행한 후, SELECT를 진행하기 때문에 나처럼 CTE로 분리하지 않고 하나의 쿼리로도 충분히 작성가능할 것 같다. 하나의 쿼리로 만들면 다음과 같이 만들 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1739955481002&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT HE.EMP_NO, HE.EMP_NAME, 
    CASE 
        WHEN AVG(SCORE) &amp;gt;= 96 THEN 'S'
        WHEN AVG(SCORE) &amp;gt;= 90 THEN 'A'
        WHEN AVG(SCORE) &amp;gt;= 80 THEN 'B'
        ELSE 'C'
    END AS GRADE, 
    CASE 
        WHEN AVG(SCORE) &amp;gt;= 96 THEN SAL * 20 / 100
        WHEN AVG(SCORE) &amp;gt;= 90 THEN SAL * 15 / 100
        WHEN AVG(SCORE) &amp;gt;= 80 THEN SAL * 10 / 100
        ELSE 0
    END AS BONUS
FROM HR_EMPLOYEES HE
JOIN HR_GRADE HG
ON HE.EMP_NO = HG.EMP_NO
GROUP BY EMP_NO
ORDER BY EMP_NO ASC&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;훨씬 깔끔하다!&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;20. 부서별 평균 연봉 조회하기&lt;/h2&gt;
&lt;pre id=&quot;code_1739954191145&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT HD.DEPT_ID, HD.DEPT_NAME_EN, 
    ROUND(AVG(HE.SAL), 0) AS AVG_SAL
FROM HR_EMPLOYEES HE
JOIN HR_DEPARTMENT HD
ON HE.DEPT_ID = HD.DEPT_ID
GROUP BY HD.DEPT_ID
ORDER BY AVG(HE.SAL) DESC&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그냥 GROUP BY만 쓰면 되는 쉬운 문제다. 평균연봉순으로 정렬 시, 혹시 몰라서 ROUND를 쓴 AVG_SAL을 쓰지 않고 AVG를 사용하였다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;21. 노선별 평균 역 사이 거리 조회하기 - 레벨2&lt;/h2&gt;
&lt;pre id=&quot;code_1739953666765&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT ROUTE, 
    CONCAT(ROUND(SUM(D_BETWEEN_DIST), 1), 'km') AS TOTAL_DISTANCE,
    CONCAT(ROUND(AVG(D_BETWEEN_DIST), 2), 'km') AS AVERAGE_DISTANCE
FROM SUBWAY_DISTANCE 
GROUP BY ROUTE
ORDER BY ROUND(SUM(D_BETWEEN_DIST), 1) DESC&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;계속 틀렸다고 나와서 화가 났는데 이상한 실수를 한 거였다. 틀린 이유는 CONCAT을 하고 난 후인 TOTAL_DISTANCE를 기준으로 내림차순 정렬을 해서 원하는 결과가 나오지 않았다. 예를 들어 '12km'와 '111km'는 111가 더 커야하는데, 사전순으로 정렬되면서 꼬일 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;CONCAT도 조심해서 써야겠다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;+) 소수점 둘째자리에서 반올림하는 경우, ROUND(X, 1) 이렇게 1을 써줘야한다. 나타내고자 하는 소수점 개수를 적어줘야한느 거 주의!&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;22. 물고기 종류 별 잡은 수 구하기 - 레벨2&lt;/h2&gt;
&lt;pre id=&quot;code_1739953188995&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT COUNT(*) AS FISH_COUNT, FNI.FISH_NAME
FROM FISH_INFO FI
JOIN FISH_NAME_INFO FNI
ON FI.FISH_TYPE = FNI.FISH_TYPE
GROUP BY FNI.FISH_NAME
ORDER BY FISH_COUNT DESC&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그냥 JOIN과 GROUP BY만 수행하면 전혀 어렵지 않은 문제다. 딱히 설명할 게 없다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;23. 월별 잡은 물고기 수 구하기 - 레벨2&lt;/h2&gt;
&lt;pre id=&quot;code_1739952843907&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT COUNT(*) AS FISH_COUNT, 
    MONTH(TIME) AS MONTH
FROM FISH_INFO
GROUP BY MONTH(TIME)
ORDER BY MONTH ASC&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;GROUP BY만 잘 쓰면 어렵지 않은 문제다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;MONTH() 함수를 사용하면 월을 출력할 때 0이 붙지 않고, GROUPBY를 사용했기 때문에 잡은 물고기가 없는 MONTH는 출력되지 않는다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;+) 더 알아가기: 만약 1을 01로 표시하고, 2를 02로 표시하고 싶은 경우 LPAD라는 함수를 사용하면 된다. LPAD(MONTH(TIME), 2, '0') 이런식으로 자릿수와 왼쪽에 채울 문자를 넣어주는 식으로 사용한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;24. 특정 조건을 만족하는 물고기별 수와 최대 길이 구하기 - 레벨3&lt;/h2&gt;
&lt;pre id=&quot;code_1739952676929&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT COUNT(*) AS FISH_COUNT, 
    MAX(IFNULL(LENGTH, 10)) AS MAX_LENGTH, 
    FISH_TYPE
FROM FISH_INFO
GROUP BY FISH_TYPE
HAVING AVG(IFNULL(LENGTH, 10)) &amp;gt;= 33
ORDER BY FISH_TYPE&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HAVING에 AVG를 넣고 해당하는 것만 출력되게 만들었다. 어렵지 않은 문제다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>database</category>
      <author>fladi</author>
      <guid isPermaLink="true">https://fladi.tistory.com/452</guid>
      <comments>https://fladi.tistory.com/452#entry452comment</comments>
      <pubDate>Wed, 19 Feb 2025 17:58:15 +0900</pubDate>
    </item>
    <item>
      <title>[MySQL] Programmers SQL 고득점 Kit 풀기 - GROUP BY(1)</title>
      <link>https://fladi.tistory.com/451</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이전 글&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://fladi.tistory.com/450&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://fladi.tistory.com/450&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GROUP BY가 어렵다고 해서 SELECT, STRING, JOIN을 모두 풀고 도전하려고 했다. 하지만 문제들을 풀다보니 GROUP BY 연관 문제가 계속 나와서 미리 예습을 하고 온 느낌이다. 이제 GROUP BY만 끝내면 SQL kit 완주다! 조금 만 더 힘내야지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제풀이 사이트: &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/parts/17044&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/parts/17044&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+) 다시 되뇌고 시작하겠다! &lt;span style=&quot;background-color: #ffffff; color: #323232; text-align: left;&quot;&gt;SQL 실행 순서는 FROM -&amp;gt; WHERE -&amp;gt; GROUP BY -&amp;gt; HAVING -&amp;gt; SELECT -&amp;gt; ORDER BY&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 식품분류별 가장 비싼 식품의 정보 조회하기 - 레벨4&lt;/h2&gt;
&lt;pre id=&quot;code_1739722992935&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT A.CATEGORY, A.PRICE, A.PRODUCT_NAME
FROM FOOD_PRODUCT A
JOIN
(
    SELECT CATEGORY, MAX(PRICE) AS MAX_PRICE
    FROM FOOD_PRODUCT
    WHERE CATEGORY IN ('과자', '국', '김치', '식용유')
    GROUP BY CATEGORY
) B
ON A.CATEGORY = B.CATEGORY 
    AND A.PRICE = B.MAX_PRICE    
ORDER BY PRICE DESC;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GROUP BY가 익숙하지 않다는게 느껴진다. 차근차근 어떻게든 풀어내긴 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;CATEGORY를 기준으로 GROUP BY를 수행하여 카테고리별 최대 가격을 구한다
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;('과자', '국', '김치', '식용유')에 포함되는 카테고리만 포함해야한다&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;카테고리의 최대 가격에 해당하는 상품의 이름을 조회하기 위해 JOIN을 다시 수행해준다&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 카테고리에 최대 가격을 가지는 상품이 여러 개인 경우를 고려해야할 것 같았지만, 그냥 돌렸더니 정답처리가 되었다. 테케가 그렇게 구성되어 있어서 그런듯하다. 내 풀이는 최대 가격을 가지는 상품이 여러 개이면 전부 출력하도록 작성되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;RANK 풀이&amp;gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739723586162&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT CATEGORY, PRICE, PRODUCT_NAME
FROM (
    SELECT 
        CATEGORY, 
        PRICE, 
        PRODUCT_NAME,
        RANK() OVER (PARTITION BY CATEGORY ORDER BY PRICE DESC) as PRICE_RANK
    FROM FOOD_PRODUCT
    WHERE CATEGORY IN ('과자', '국', '김치', '식용유')
) A
WHERE PRICE_RANK = 1
ORDER BY PRICE DESC;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 더 개선하면 RANK()를 사용할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;RANK()함수를 사용한다
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;CATEGORY별로 파티션되고, 이는 PRICE 내림차순으로 정렬된다&lt;/li&gt;
&lt;li&gt;(이 때 ORDER BY는 각 파티션 내에서 한 번만 수행된다)&lt;/li&gt;
&lt;li&gt;매겨진 순위를 PRICE_RANK로 저장한다&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;PRICE_RANK를 1로 지정하여 최대값(우선순위 1)을 가지는 상품을 찾아낸다&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 풀이도 마찬가지로 가장 높은 가격의 상품이 여러 개라면 여러 개가 출력된다. RANK()는 동일한 가격이라면 같은 RANK를 부여하기 때문이다. 딱 하나만 출력하고 싶다면 &lt;u&gt;&lt;b&gt;RANK()&lt;/b&gt;&lt;/u&gt;가 아닌 &lt;u&gt;&lt;b&gt;ROW_NUMBER()&lt;/b&gt;&lt;/u&gt;를 사용하면 같은 값이라도 다른 번호가 부여된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;ROW_NUMBER()&lt;/b&gt;&lt;/u&gt;를 사용할 때 같은 가격이라면 사전순으로 작은 이름을 출력하도록 하고싶다면, 해당 조건을 ORDER BY에 추가하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 대여 횟수가 많은 자동차들의 월별 대여 횟수 구하기 - 레벨3&lt;/h2&gt;
&lt;pre id=&quot;code_1739721436290&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT MONTH(START_DATE) AS MONTH, CAR_ID, COUNT(*) AS RECORDS
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY 
WHERE CAR_ID IN 
(
    SELECT CAR_ID
    FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY 
    WHERE START_DATE &amp;gt;= '2022-08-01' AND START_DATE&amp;lt; '2022-11-01'
    GROUP BY CAR_ID
    HAVING COUNT(CAR_ID) &amp;gt; 4
) 
AND START_DATE &amp;gt;= '2022-08-01' AND START_DATE&amp;lt; '2022-11-01'
GROUP BY MONTH, CAR_ID
HAVING RECORDS &amp;gt; 0
ORDER BY MONTH, CAR_ID DESC

-- START_DATE가 2022.08 ~ 2022.10 인 count가 5회 이상인 자동차 ID 조회
-- 월, 자동차 별 대여횟수 COUNT&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차근차근 풀어나가니 문제가 풀렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;START_DATE를 기준으로 2022년 8월부터 2022년 10월까지 총 대여 횟수가 5회 이상인 자동차의 CAR_ID를 조회해야한다
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;CAR_ID를 기준으로 GROUP BY를 수행하고,&lt;/li&gt;
&lt;li&gt;해당 일자 내에 포함되는 레코드 개수가 4개를 초과(5개 이상)하는 것만 조회한다&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;2022년 8월부터 2022년 10월까지 범위에 포함되면서, 1번에서 구한 CAR_ID에 해당하는 레코드를 조회한다
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;MONTH와 CAR_ID를 기준으로 GROUP BY를 수행하고,&lt;/li&gt;
&lt;li&gt;개수가 0개 이상인 것들만 출력한다&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #323232;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&amp;lt;좀 더 개선한 코드&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739722078143&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT MONTH(START_DATE) AS MONTH, CAR_ID, COUNT(*) AS RECORDS
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY 
WHERE CAR_ID IN 
(
    SELECT CAR_ID
    FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY 
    WHERE START_DATE BETWEEN '2022-08-01' AND '2022-10-31'
    GROUP BY CAR_ID
    HAVING COUNT(CAR_ID) &amp;gt; 4
) 
AND START_DATE BETWEEN '2022-08-01' AND '2022-10-31'
GROUP BY MONTH, CAR_ID
# HAVING RECORDS &amp;gt; 0 레코드가 없으면 애초에 조회되지 않음
ORDER BY MONTH, CAR_ID DESC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BETWEEN A AND B 문법으로 대체하였고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GROUP BY의 경우 레코드가 없으면 애초에 조회되지 않기 때문에 0이 나올 수 없다. 그래서 HAVING은 딱히 필요없다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 자동차 대여 기록에서 대여중 / 대여 가능 여부 구분하기 - 레벨3&lt;/h2&gt;
&lt;pre id=&quot;code_1739725145637&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT 
    CAR_ID,
    MAX(
        CASE 
        WHEN '2022-10-16' BETWEEN START_DATE AND END_DATE THEN '대여중'
        ELSE '대여 가능'
        END
    ) AS &quot;AVAILABILITY&quot;
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
GROUP BY CAR_ID
ORDER BY CAR_ID DESC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;푸는걸 실패하고 다른 풀이를 참고하였다. 상당히 참신한 풀이라서 마음에 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;일단 CAR_ID로 GROUP BY를 수행한다&lt;/li&gt;
&lt;li&gt;만약 대여중인 상태라면 AVAILABILITY컬럼에 '대여중'을 넣고, 아니면 '대여 가능'을 넣도록 한다
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&quot;대여중&quot;이 &quot;대여 가능&quot; 문자보다 더 사전순으로 뒤에있기 때문에 MAX함수를 사용한다&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;출력한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알고리즘을 풀 때는 몇 번 사용해본 적 있는 전략인데, SQL에서도 필요할 때 사용해야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;다른 풀이&amp;gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739725541936&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;WITH RENTAL_STATUS AS (
    SELECT DISTINCT CAR_ID
    FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
    WHERE '2022-10-16' BETWEEN START_DATE AND END_DATE
)

SELECT 
    h.CAR_ID,
    CASE 
        WHEN r.CAR_ID IS NOT NULL THEN '대여중'
        ELSE '대여 가능'
    END AS &quot;AVAILABILITY&quot;
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY h
LEFT JOIN RENTAL_STATUS r ON h.CAR_ID = r.CAR_ID
GROUP BY h.CAR_ID
ORDER BY h.CAR_ID DESC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 같은 참신한 풀이를 생각해낼 수 없다면 CTE를 만드는 방법도 있다. 이게 가장 쉽게 떠올릴 수 있는 방법인 것 같다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대여중인 모든 CAR_ID를 중복없이 조회하는 CTE를 만든다&lt;/li&gt;
&lt;li&gt;CAR_RENTAL_COMPANY_RENTAL_HISTORY와 LEFT JOIN을 수행하여 대여중이지 않은 CAR_ID의 경우 r.CAR_ID가 NULL이 나오도록 한다&lt;/li&gt;
&lt;li&gt;CAR_ID를 기준으로 GROUP BY를 수행한다&lt;/li&gt;
&lt;li&gt;r.CAR_ID가 NULL인 경우 '대여 가능'으로 표시한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 자동차 종류 별 특정 옵션이 포함된 자동차 수 구하기 - 레벨2&lt;/h2&gt;
&lt;pre id=&quot;code_1739726605687&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT CAR_TYPE, COUNT(*) AS CARS
FROM CAR_RENTAL_COMPANY_CAR 
WHERE OPTIONS like '%통풍시트%' OR OPTIONS like '%열선시트%' OR OPTIONS like '%가죽시트%'
GROUP BY CAR_TYPE
ORDER BY CAR_TYPE ASC;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;like를 여러 개 사용할 수 있는 방법이 있을 것 같은데, 모르겠어서 다 이어붙였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 여러 개가 포함될 수 있는 경우에는 REGEXP를 사용하면 더 간결해질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1739726728846&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT CAR_TYPE, COUNT(*) AS CARS
FROM CAR_RENTAL_COMPANY_CAR 
WHERE OPTIONS REGEXP '통풍시트|열선시트|가죽시트'
GROUP BY CAR_TYPE
ORDER BY CAR_TYPE;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규표현식에 익숙하다면 이 방법이 더 편할 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 성분으로 구분한 아이스크림 총 주문량&lt;/h2&gt;
&lt;pre id=&quot;code_1739728682894&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT I.INGREDIENT_TYPE, SUM(TOTAL_ORDER) AS TOTAL_ORDER
FROM FIRST_HALF F
JOIN ICECREAM_INFO I
ON F.FLAVOR = I.FLAVOR
GROUP BY I.INGREDIENT_TYPE
ORDER BY TOTAL_ORDER ASC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GROUP BY만 안다면 어렵지 않게 풀린다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 저자 별 카테고리 별 매출액 집계하기 - 레벨4&lt;/h2&gt;
&lt;pre id=&quot;code_1739783127969&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT A.AUTHOR_ID AS AUTHOR_ID, A.AUTHOR_NAME, B.CATEGORY,
    SUM(BS.SALES * B.PRICE) AS TOTAL_SALES
FROM BOOK_SALES BS
JOIN BOOK B
JOIN AUTHOR A
ON BS.BOOK_ID = B.BOOK_ID   
    AND B.AUTHOR_ID = A.AUTHOR_ID
WHERE BS.SALES_DATE like &quot;2022-01%&quot;
GROUP BY A.AUTHOR_ID, B.CATEGORY
ORDER BY AUTHOR_ID ASC, CATEGORY DESC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나하나 JOIN을 해주고, GROUP BY로만 잘 묶으면 어렵지 않은 문제다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 카테고리 별 도서 판매량 집계하기 - 레벨3&lt;/h2&gt;
&lt;pre id=&quot;code_1739783628872&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT B.CATEGORY, SUM(BS.SALES) AS TOTAL_SALES
FROM BOOK_SALES BS
JOIN BOOK B
ON BS.BOOK_ID = B.BOOK_ID
WHERE BS.SALES_DATE like '2022-01%'
GROUP BY B.CATEGORY
ORDER BY CATEGORY ASC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JOIN이랑 GRUOP BY의 개념만 알면 1도 어렵지않다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 즐겨찾기가 가장 많은 식당 정보 출력하기&lt;/h2&gt;
&lt;pre id=&quot;code_1739783990352&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT FOOD_TYPE, REST_ID, REST_NAME, FAVORITES
FROM (
    SELECT FOOD_TYPE, REST_ID, REST_NAME, FAVORITES, 
        RANK() OVER (PARTITION BY FOOD_TYPE ORDER BY FAVORITES DESC) AS FRANK
    FROM REST_INFO R
) A
WHERE A.FRANK = 1
ORDER BY FOOD_TYPE DESC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 풀었던 문제에서 배운 RANK()를 사용했다. ROW_NUMBER가 아닌 RANK를 사용했기 때문에 즐겨찾기가 가장 많은 식당은 2개 이상 나올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 문법이 익숙하지 않지만 여러 번 사용하다보면 외워질 듯 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;다른 풀이&amp;gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739784271029&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;WITH TOP_RESTAURANTS AS (
    SELECT FOOD_TYPE, REST_ID, REST_NAME, FAVORITES,
        FIRST_VALUE(REST_ID) OVER (
            PARTITION BY FOOD_TYPE 
            ORDER BY FAVORITES DESC
        ) AS TOP_REST_ID
    FROM REST_INFO
)

SELECT FOOD_TYPE, REST_ID, REST_NAME, FAVORITES
FROM TOP_RESTAURANTS
WHERE REST_ID = TOP_REST_ID
ORDER BY FOOD_TYPE DESC;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바뀐건 CTE로 뺀 것과 FIRST_VALUE를 사용한 것밖에 없다.&amp;nbsp;FIRST_VALUE는 파티션 내에서 정렬된 첫 번째 값을 가져오는 함수이다. 파티션별로 가장 인기 있는(TOP_REST_ID가 1인) 값을 가져올 수 있다. 그래서 WHERE절에 REST_ID = TOP_REST_ID로 가져오면 정답이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 조건에 맞는 사용자와 총 거래금액 조회하기&lt;/h2&gt;
&lt;pre id=&quot;code_1739784836104&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT U.USER_ID, U.NICKNAME, SUM(B.PRICE) AS TOTAL_SALES
FROM USED_GOODS_BOARD B
JOIN USED_GOODS_USER U
ON B.WRITER_ID = U.USER_ID
WHERE B.STATUS = 'DONE'
GROUP BY U.USER_ID 
HAVING TOTAL_SALES &amp;gt;= 700000
ORDER BY TOTAL_SALES ASC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HAVING만 알면 어렵지 않다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;HAVING에는 TOTAL_SALES라는 Alias를 사용이 가능하다. 쿼리 실행 순서는 JOIN -&amp;gt; WHERE -&amp;gt; GROUP BY -&amp;gt; HAVING -&amp;gt; SELECT(Alias 생성) -&amp;gt; ORDER BY 순으로 실행되는데, 왜 사용이 가능할까?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL 파서가 쿼리를 분석할 때 SELECT 절을 먼저 읽어서 alias를 인식한다. 그래서 실제 실행은 위 순서대로 진행되지만, GROUP BY와 HAVING 에서는 SELECT 에서 정의된 alias를 참조가능하도록 특별히 허용한다고 한다. GROUP BY와 HAVING이 집계 결과를 다루는 특수한 목적을 가진 절이기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 풀이가 통과되는 DBMS인 MySQL은 GROUP BY와 HAVING에 SELECT에서 정의한 ALIAS를 사용할 수 있도록 특별히 허용해준다는 걸 알 수 있다. 하지만 표준에서는 사용할 수 없다는 사실을 기억하자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. 진료과별 총 예약 횟수 출력하기&lt;/h2&gt;
&lt;pre id=&quot;code_1739785697253&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT MCDP_CD AS `진료과코드`, COUNT(*) AS `5월예약건수`
FROM APPOINTMENT
WHERE APNT_YMD like '2022-05%'
GROUP BY MCDP_CD
ORDER BY `5월예약건수`, `진료과코드`&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 문제를 다 풀고오면 너무 쉽게 느껴질 듯하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;11. 고양이와 개는 몇 마리 있을까&lt;/h2&gt;
&lt;pre id=&quot;code_1739787765511&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT ANIMAL_TYPE, COUNT(*) AS count
FROM ANIMAL_INS 
WHERE ANIMAL_TYPE IN ('Cat', 'Dog')
GROUP BY ANIMAL_TYPE
ORDER BY ANIMAL_TYPE&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 쉬워서 재미없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ANIMAL_TYPE을 선별하는 과정은 HAVING절 보다 WHERE절에 하는게 성능이 더 좋다. 그룹화 전에 필터링을 수행하기 때문에 적은 데이터만 필터링하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;12. 동명 동물 수 찾기&lt;/h2&gt;
&lt;pre id=&quot;code_1739788006680&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT NAME, COUNT(*) AS `COUNT`
FROM ANIMAL_INS 
WHERE NAME IS NOT NULL
GROUP BY NAME
HAVING COUNT(*) &amp;gt; 1
ORDER BY NAME&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름이 NULL인 경우도 COUNT에 집계된다. NULL인 동물은 카운트하지 않는다고 했으니 IS NOT NULL을 꼭 붙여줘야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>database</category>
      <author>fladi</author>
      <guid isPermaLink="true">https://fladi.tistory.com/451</guid>
      <comments>https://fladi.tistory.com/451#entry451comment</comments>
      <pubDate>Mon, 17 Feb 2025 19:28:29 +0900</pubDate>
    </item>
    <item>
      <title>[MySQL] Programmers SQL 고득점 Kit 풀기 - JOIN</title>
      <link>https://fladi.tistory.com/450</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이전 글&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://fladi.tistory.com/449&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://fladi.tistory.com/449&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 조인이다! 어려운 문제들이 많을 것 같아서 기대가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제풀이 사이트: &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/parts/17046&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/parts/17046&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 특정 기간동안 대여 가능한 자동차들의 대여비용 구하기 - 레벨4&lt;/h2&gt;
&lt;pre id=&quot;code_1739455535062&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT 
    C.CAR_ID,
    C.CAR_TYPE,
    ROUND(C.RAW_FEE * (100 - IFNULL(P.DISCOUNT_RATE, 0)) / 100) AS FEE
FROM (
    SELECT CAR_ID, CAR_TYPE
        DAILY_FEE * 30 AS RAW_FEE,
        '30일 이상' AS DURATION_TYPE 
    FROM CAR_RENTAL_COMPANY_CAR 
    WHERE 1=1
        AND CAR_TYPE IN ('세단', 'SUV')
        AND CAR_ID NOT IN (
            SELECT CAR_ID
            FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY 
            WHERE START_DATE &amp;lt;= '2022-11-30' AND END_DATE &amp;gt;= '2022-11-01'
        )
) C
LEFT JOIN CAR_RENTAL_COMPANY_DISCOUNT_PLAN P
ON C.CAR_TYPE = P.CAR_TYPE AND C.DURATION_TYPE = P.DURATION_TYPE
WHERE 1=1
    AND (C.DAILY_FEE * 30 * (100 - IFNULL(P.DISCOUNT_RATE, 0)) / 100) &amp;gt;= 500000 
    AND (C.DAILY_FEE * 30 * (100 - IFNULL(P.DISCOUNT_RATE, 0)) / 100) &amp;lt; 2000000
ORDER BY FEE DESC, CAR_TYPE ASC, CAR_ID DESC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;굉장히 더럽게 짰지만 맞긴했다.&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;HISTORY에서 2022-11-01 ~ 2022-11-30 사이에 대여내역이 있는 CAR_ID를 조회한다&lt;/li&gt;
&lt;li&gt;(1) 세단과 SUV이면서, (2)2022-11-01 ~ 2022-11-30 사이에 대여내역이 있는 CAR_ID가 아닌 것을 CAR에서 조회한다
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이 때 30일 탑승 가격을 미리 구한다&lt;/li&gt;
&lt;li&gt;PLAN과 JOIN을 위해 '30일 이상'이 포함되는 컬럼도 추가해줬다&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;PLAN과 조인한다. 기준은 '30일 이상'이면서 CAR_TYPE이 같은 것들이다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;해당하는 할인이 없으면 NULL로 나오도록 LEFT JOIN을 수행하였고, IFNULL로 처리해줬다&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WHERE절에 FEE를 사용할 수 없어서 저렇게 적었는데, 이유는 WHERE절이 SELECT의 별칭이 만들어지기 전 미리 실행되기 때문이었다. (SQL 실행 순서는 FROM -&amp;gt; WHERE -&amp;gt; GROUP BY -&amp;gt; HAVING -&amp;gt; SELECT -&amp;gt; ORDER BY이다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FEE라는 alias를 사용하고 싶다면 Common Table Expression(CTE)를 이용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CTE를 사용하여 FEE를 먼저 계산하고, 이후 WHERE절로 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1739456448942&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;WITH TMP AS (
    SELECT 
        C.CAR_ID,
        C.CAR_TYPE,
        ROUND(C.RAW_FEE * (100 - IFNULL(P.DISCOUNT_RATE, 0)) / 100) AS FEE
    FROM (
        SELECT CAR_ID, CAR_TYPE,
            DAILY_FEE * 30 AS RAW_FEE,
            '30일 이상' AS DURATION_TYPE 
        FROM CAR_RENTAL_COMPANY_CAR 
        WHERE 1=1
            AND CAR_TYPE IN ('세단', 'SUV')
            AND CAR_ID NOT IN (
                SELECT CAR_ID
                FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY 
                WHERE START_DATE &amp;lt;= '2022-11-30' AND END_DATE &amp;gt;= '2022-11-01'
            )
    ) C
    LEFT JOIN CAR_RENTAL_COMPANY_DISCOUNT_PLAN P
    ON C.CAR_TYPE = P.CAR_TYPE AND C.DURATION_TYPE = P.DURATION_TYPE
)

SELECT * 
FROM TMP
WHERE 1=1
    AND FEE &amp;gt;= 500000 
    AND FEE &amp;lt; 2000000
ORDER BY FEE DESC, CAR_TYPE ASC, CAR_ID DESC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 더 개선한 코드는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1739457582171&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;WITH TMP AS (
    SELECT 
        C.CAR_ID,
        C.CAR_TYPE,
        ROUND(C.DAILY_FEE * 30 * (100 - IFNULL(P.DISCOUNT_RATE, 0)) / 100) AS FEE
    FROM CAR_RENTAL_COMPANY_CAR C
    LEFT JOIN (
        SELECT *
        FROM CAR_RENTAL_COMPANY_DISCOUNT_PLAN
        WHERE DURATION_TYPE = '30일 이상'
    ) P ON C.CAR_TYPE = P.CAR_TYPE
    WHERE C.CAR_TYPE IN ('세단', 'SUV')
        AND C.CAR_ID NOT IN (
            SELECT CAR_ID
            FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY 
            WHERE START_DATE &amp;lt;= '2022-11-30' AND END_DATE &amp;gt;= '2022-11-01'
        )
)

SELECT * 
FROM TMP
WHERE 1=1
    AND FEE &amp;gt;= 500000 
    AND FEE &amp;lt; 2000000
ORDER BY FEE DESC, CAR_TYPE ASC, CAR_ID DESC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;30일이상인 PLAN만 미리 조회하는 로직만 추가했다. 나머지는 비슷하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 5월 식품들의 총매출 조회하기&lt;span&gt;&amp;nbsp;&lt;/span&gt;- 레벨4&lt;/h2&gt;
&lt;pre id=&quot;code_1739458335762&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT 
    P.PRODUCT_ID, 
    P.PRODUCT_NAME, 
    SUM(P.PRICE  * O.AMOUNT) AS TOTAL_SALES
FROM FOOD_PRODUCT P
JOIN FOOD_ORDER O
    ON P.PRODUCT_ID = O.PRODUCT_ID
WHERE O.PRODUCE_DATE like '2022-05-%'
GROUP BY PRODUCT_ID
ORDER BY TOTAL_SALES DESC, PRODUCT_ID ASC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GROUP BY를 잘 안다면 어렵지 않게 풀 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 주문량이 많은 아이스크림들 조회하기 - 레벨4&lt;/h2&gt;
&lt;pre id=&quot;code_1739465539105&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT F.FLAVOR
FROM FIRST_HALF F
    LEFT JOIN (SELECT FLAVOR, sum(TOTAL_ORDER) AS JULY_TOTAL_ORDER
                FROM JULY
                GROUP BY FLAVOR) J
        ON F.FLAVOR = J.FLAVOR
ORDER BY TOTAL_ORDER + IFNULL(JULY_TOTAL_ORDER, 0) DESC
LIMIT 3;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 이해가 안돼서 조금 고생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FIRST_HALF 테이블은 FLAVOR를 PK로 가지고 있고, FLAVOR당 총 주문량이 저장되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JULY는 SHIPMENT_ID를 PK로 가지기 때문에 FLAVOR가 여러 개 나올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표는 JULY에서 FLAVOR별로 주문량을 구하고, FIRST_HALF값이랑 더하는 것이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;JULY 테이블에서 FLAVOR당 총 주문량을 GROUP BY로 구해준다&lt;/li&gt;
&lt;li&gt;FIRST_HALF 테이블에는 FLAVOR가 PK로 저장되어있기 때문에 FLAVOR당 총 주문량이 이미 저장되어 있다&lt;/li&gt;
&lt;li&gt;FLAVOR를 기준으로 JOIN을 수행하고, ORDER BY 한 결과 상위 3개를 출력한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;JULY&lt;span&gt; &lt;/span&gt;&lt;/span&gt;테이블에 있는 flavor가 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;FIRST_HALF&lt;span&gt; &lt;/span&gt;&lt;/span&gt;테이블의 flavor로 없을 수 있는 경우도 고려해야한다는 생각이 들었다. 둘 다 NULL이 나올 수 있도록 처리하기 위해 FULL OUTER JOIN을 사용하는 게 맞는 것 같았다. 하지만 mysql 에서는 FULL OUTER JOIN을 지원하지 않았고, UNION을 사용하여 이를 구현하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1739466002595&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT FLAVOR
FROM (
    SELECT F.FLAVOR, (IFNULL(F.TOTAL_ORDER, 0) + IFNULL(J.JULY_TOTAL_ORDER, 0)) AS TOTAL
    FROM FIRST_HALF F
        LEFT JOIN (SELECT FLAVOR, SUM(TOTAL_ORDER) AS JULY_TOTAL_ORDER
                  FROM JULY
                  GROUP BY FLAVOR) J
        ON F.FLAVOR = J.FLAVOR
    UNION
    SELECT J.FLAVOR, (IFNULL(F.TOTAL_ORDER, 0) + IFNULL(J.JULY_TOTAL_ORDER, 0)) AS TOTAL
    FROM FIRST_HALF F
        RIGHT JOIN (SELECT FLAVOR, SUM(TOTAL_ORDER) AS JULY_TOTAL_ORDER
                   FROM JULY
                   GROUP BY FLAVOR) J
        ON F.FLAVOR = J.FLAVOR
) AS COMBINED
ORDER BY TOTAL DESC
LIMIT 3;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 FLAVOR가 서로 없는 값을 가져도 집계에 포함이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(왜 그냥 INNER JOIN으로도 정답처리가 되는지 잘 모르겠다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;더 좋은 풀이&amp;gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740071752946&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT FLAVOR
FROM (
    SELECT *
    FROM FIRST_HALF 
    UNION
    SELECT *
    FROM JULY
) A
GROUP BY FLAVOR
ORDER BY SUM(TOTAL_ORDER) DESC
LIMIT 3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 풀이가 딱 의도대로 푼 풀이가 아닐까 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UNION으로 합치고, GROUP BY로 정렬하면 쉽게 풀린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 조건에 맞는 도서와 저자 리스트 출력하기 - 레벨2&lt;/h2&gt;
&lt;pre id=&quot;code_1739467051933&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT BOOK_ID, AUTHOR_NAME, DATE_FORMAT(PUBLISHED_DATE, '%Y-%m-%d') AS PUBLISHED_DATE
FROM BOOK B
JOIN AUTHOR A
ON B.AUTHOR_ID = A.AUTHOR_ID
WHERE B.CATEGORY = '경제'
ORDER BY PUBLISHED_DATE ASC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어렵지 않다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 그룹별 조건에 맞는 식당 목록 출력하기 -&amp;nbsp;레벨4&lt;/h2&gt;
&lt;pre id=&quot;code_1739509708518&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT M.MEMBER_NAME, R.REVIEW_TEXT, 
    DATE_FORMAT(R.REVIEW_DATE, &quot;%Y-%m-%d&quot;) as REVIEW_DATE
FROM REST_REVIEW R
JOIN MEMBER_PROFILE M
ON R.MEMBER_ID = M.MEMBER_ID
WHERE R.MEMBER_ID IN
(
    SELECT MEMBER_ID
    FROM REST_REVIEW
    GROUP BY MEMBER_ID
    HAVING COUNT(*) = (
        SELECT COUNT(*) cnt
        FROM REST_REVIEW
        GROUP BY MEMBER_ID
        ORDER BY cnt DESC
        LIMIT 1
    )
)
ORDER BY REVIEW_DATE ASC, REVIEW_TEXT ASC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 좋은 방법이 있을 거라고 생각했지만 떠오르지 않아서 아는 범위 내에서 열심히 답을 출력했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;가장 많은 리뷰 수 구하기: REST_REVIEW에서 MEMBER_ID 별로 GROUP BY를 수행하여 사람 당 쓴 리뷰 수를 구한다. 이 때 리뷰를 쓴 개수가 많은 순으로 내림차순 정렬 후 하나만 뽑았다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;가장 많은 리뷰 수를 가지는 MEMBER_ID를 구했다. 가장 많은 리뷰 수를 가지는 멤버는 여러 명이 될 수 있다&lt;/li&gt;
&lt;li&gt;해당 MEMBER_ID의 리뷰를 출력한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IN이나 서브쿼리보다는 JOIN을 사용하는 게 성능에 도움이 된다고 한다. (옵티마이저가 최적화하기 용이함) 내 풀이는 서브쿼리를 3개나 넣어서 조금 부끄러웠다. 분명 더 좋은 풀이가 있을 거라고 생각해 찾아봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1739510178788&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;WITH ReviewCounts AS (
    SELECT MEMBER_ID, COUNT(*) as review_count
    FROM REST_REVIEW
    GROUP BY MEMBER_ID
),
MaxReviewer AS (
    SELECT MAX(review_count) as max_count
    FROM ReviewCounts
)

SELECT 
    M.MEMBER_NAME, 
    R.REVIEW_TEXT, 
    DATE_FORMAT(R.REVIEW_DATE, '%Y-%m-%d') as REVIEW_DATE
FROM REST_REVIEW R
JOIN MEMBER_PROFILE M ON R.MEMBER_ID = M.MEMBER_ID
JOIN ReviewCounts RC ON R.MEMBER_ID = RC.MEMBER_ID
JOIN MaxReviewer MR ON RC.review_count = MR.max_count
ORDER BY R.REVIEW_DATE ASC, R.REVIEW_TEXT ASC;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 풀이를 CTE로 분리하고, COUNT(*) 연산 수를 줄인 풀이이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;MEMBER_ID별로 리뷰 수를 구하는 ReviewCounts 를 만든다&lt;/li&gt;
&lt;li&gt;ReviewCounts CTE에서 가장 많은 리뷰 수를 구하는 MaxReviewer를 만든다&lt;/li&gt;
&lt;li&gt;JOIN을 통해 MaxReviewer와 RevieweCounts에서 최대 리뷰 수를 가지는 MEBMER_ID를 뽑아낸다&lt;/li&gt;
&lt;li&gt;3번과 REST_REVIEW, MEMBER_PROFILE 을 JOIN하여 리뷰를 구해낸다&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설명보다 코드를 보면 쉽게 이해할 수 있을 거라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(+ 인터넷에 검색해보면 가장 많은 수의 리뷰를 적은 사람이 1명이라고 가정하고 푼 경우가 많고, 프로그래머스는 해당 답을 정답처리 해주는 것 같다. 문제를 읽으면 가장 많은 수의 리뷰를 적은 사람이 무조건 1명이라는 제약조건이 없다. 실제 쿼리를 작성해야할 때는 이 경우를 꼭 고려해야하기 때문에 문제를 풀 때부터 엣지케이스를 잘 생각하는 습관을 들이면 좋을 것 같다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6, 없어진 기록 찾기 -&amp;nbsp;레벨3&lt;/h2&gt;
&lt;pre id=&quot;code_1739511188109&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT ANIMAL_ID, NAME
FROM ANIMAL_OUTS
WHERE ANIMAL_ID NOT IN 
(
    SELECT I.ANIMAL_ID
    FROM ANIMAL_INS I
    LEFT JOIN ANIMAL_OUTS O
    ON I.ANIMAL_ID = O.ANIMAL_ID
)
ORDER BY ANIMAL_ID ASC;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OUTS와 INS의 차집합을 구하면 되는 문제인데 어떻게 구현하면 좋을지 떠오르지 않아 서브쿼리를 사용하였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차집합은 아래와 같이 사용하면 더 쉽게 구할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1739513263694&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT O.ANIMAL_ID, O.NAME
FROM ANIMAL_OUTS O
LEFT JOIN ANIMAL_INS I ON O.ANIMAL_ID = I.ANIMAL_ID
WHERE I.ANIMAL_ID IS NULL
ORDER BY O.ANIMAL_ID ASC;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외워두면 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 있었는데요 없었습니다 -&amp;nbsp;레벨3&lt;/h2&gt;
&lt;pre id=&quot;code_1739513665899&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 코드를 입력하세요
SELECT OUTS.ANIMAL_ID, OUTS.NAME
FROM ANIMAL_OUTS OUTS
JOIN ANIMAL_INS INS
ON OUTS.ANIMAL_ID = INS.ANIMAL_ID
WHERE INS.DATETIME &amp;gt; OUTS.DATETIME
ORDER BY INS.DATETIME&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JOIN만 잘하면 되는 문제다. 어렵지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입양일이 존재해야하기 때문에 INNER JOIN을 사용하였고, 보호 시작일보다 입양일이 더 빠른 동물만 조회하였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 오랜 기간 보호한 동물(1) -&amp;nbsp;레벨3&lt;/h2&gt;
&lt;pre id=&quot;code_1739513919169&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT I.NAME, I.DATETIME
FROM ANIMAL_INS I
LEFT JOIN ANIMAL_OUTS O
ON I.ANIMAL_ID = O.ANIMAL_ID
WHERE O.ANIMAL_ID IS NULL
ORDER BY I.DATETIME ASC
LIMIT 3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차집합을 구하기만 하면 되는 어렵지 않은 문제다. 6번 문제에서 사용한 차집합 구하는 방식을 사용하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 보호소에서 중성화한 동물 -&amp;nbsp;레벨4&lt;/h2&gt;
&lt;pre id=&quot;code_1739514772725&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT O.ANIMAL_ID, O.ANIMAL_TYPE, O.NAME
FROM (
    SELECT ANIMAL_ID, ANIMAL_TYPE, NAME
    FROM ANIMAL_OUTS
    WHERE SEX_UPON_OUTCOME like 'Spayed%' OR SEX_UPON_OUTCOME like 'Neutered%' 
) O
JOIN ANIMAL_INS I
ON O.ANIMAL_ID = I.ANIMAL_ID
WHERE I.ANIMAL_ID NOT IN (
    SELECT ANIMAL_ID
    FROM ANIMAL_INS
    WHERE SEX_UPON_INTAKE like 'Spayed%' OR SEX_UPON_INTAKE like 'Neutered%' 
)
ORDER BY O.ANIMAL_ID&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LIKE의 반대를 쓰고싶었는데 어떻게 쓰는지 몰라 서브쿼리에 넣었다. 더 좋은 방법이 있을 것 같아 다른 풀이를 찾아봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알고보니 INTACT가 포함되면 중성화되지 않은 거였다...문제를 읽지 않아서 좀 돌아간 것 같다. 그래도 NOT LIKE를 알아둬서 나쁠 것이 없으니 다음과 같이 풀이를 개선하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1739514969417&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT O.ANIMAL_ID, O.ANIMAL_TYPE, O.NAME
FROM ANIMAL_OUTS O
JOIN ANIMAL_INS I ON O.ANIMAL_ID = I.ANIMAL_ID
WHERE (O.SEX_UPON_OUTCOME LIKE 'Spayed%' OR O.SEX_UPON_OUTCOME LIKE 'Neutered%')
AND (I.SEX_UPON_INTAKE NOT LIKE 'Spayed%' AND I.SEX_UPON_INTAKE NOT LIKE 'Neutered%')
ORDER BY O.ANIMAL_ID;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LIKE의 반대를 원하면 NOT LIKE 로 쓰면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. 상품 별 오프라인 매출 구하기&amp;nbsp;-&amp;nbsp;레벨2&lt;/h2&gt;
&lt;pre id=&quot;code_1739515253606&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT P.PRODUCT_CODE,
    SUM(O.SALES_AMOUNT * P.PRICE) AS SALES
FROM OFFLINE_SALE O
JOIN PRODUCT P
ON O.PRODUCT_ID = P.PRODUCT_ID
GROUP BY P.PRODUCT_CODE
ORDER BY SALES DESC, PRODUCT_CODE ASC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SUM과 GROUP BY만 알면 쉽게 풀리는 문제다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;11. 상품을 구매한 회원 비율 구하기 -&amp;nbsp;레벨5&lt;/h2&gt;
&lt;pre id=&quot;code_1739516820681&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT YEAR(O.SALES_DATE) AS YEAR,
        MONTH(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 YEAR(joined) = 2021),
            1
        ) AS PUCHASED_RATIO
FROM USER_INFO U
JOIN ONLINE_SALE O
ON U.USER_ID = O.USER_ID
WHERE YEAR(U.JOINED) = 2021
GROUP BY YEAR, MONTH
ORDER BY YEAR, MONTH;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;열심히 풀고자했지만 GROUP BY에 익숙하지 않아 잘 안풀렸다. 그래서 답을 참고했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;JOIN후에 YEAR, MONTH 로 GROUP BY를 수행해준다&lt;/li&gt;
&lt;li&gt;물건을 구매한 USER의 JOINED 년도는 2021년도여야 한다&lt;/li&gt;
&lt;li&gt;DISTINCT로 중복 USER를 제외한 사람의 수를 COUNT해준다(한 달에 2번 주문이 가능하니까)&lt;/li&gt;
&lt;li&gt;비율에서 분모는 가입한 날짜가 2021년도인 USER들의 수를 SELECT하는 쿼리로 구한 사람 수로 정해준다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 풀이는 조회되는 모든 튜플마다 SELECT COUNT(*) FROM USER_INFO WHERE YEAR(joined) = 2021 이 쿼리가 수행되기 때문에 비효율적이다. 이를 CTE로 빼면 한 번의 계산에 구할 수 있게 된다. 개선한 풀이는 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1739517111030&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;WITH TotalUsers AS (
    SELECT COUNT(*) as total
    FROM USER_INFO 
    WHERE YEAR(joined) = 2021
)
SELECT 
    YEAR(O.SALES_DATE) AS YEAR,
    MONTH(O.SALES_DATE) AS MONTH,
    COUNT(DISTINCT U.USER_ID) AS PUCHASED_USERS,
    ROUND(COUNT(DISTINCT U.USER_ID) / TotalUsers.total, 1) AS PUCHASED_RATIO
FROM USER_INFO U
JOIN ONLINE_SALE O ON U.USER_ID = O.USER_ID
JOIN TotalUsers
WHERE YEAR(U.JOINED) = 2021
GROUP BY YEAR, MONTH
ORDER BY YEAR, MONTH;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CTE가 정말 효자인 것 같다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;12. FrontEnd 개발자 찾기 -&amp;nbsp;레벨4&lt;/h2&gt;
&lt;pre id=&quot;code_1739515906554&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT ID, EMAIL, FIRST_NAME, LAST_NAME
FROM DEVELOPERS 
WHERE SKILL_CODE &amp;amp; (    
        SELECT SUM(CODE)
        FROM SKILLCODES 
        WHERE CATEGORY = 'Front End'
    ) &amp;gt; 0
ORDER BY ID ASC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카테고리가 Front End인 모든 스킬코드를 더한 값과 &amp;amp; 비트연산을 수행했을 때 0이 아닌 값이 나오면 프론트엔드 스택이 존재하는 개발자이다. 비트연산에 익숙하다면 어렵지 않게 풀 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WITH절로 조금 보기좋게 만들면 다음과 같이 풀 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1739516034076&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;WITH FrontEndSkills AS (
    SELECT SUM(CODE) as total_code 
    FROM SKILLCODES 
    WHERE CATEGORY = 'Front End'
)

SELECT 
    d.ID,
    d.EMAIL,
    d.FIRST_NAME,
    d.LAST_NAME
FROM DEVELOPERS d
JOIN FrontEndSkills s
WHERE d.SKILL_CODE &amp;amp; s.total_code &amp;gt; 0
ORDER BY d.ID ASC;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>database</category>
      <author>fladi</author>
      <guid isPermaLink="true">https://fladi.tistory.com/450</guid>
      <comments>https://fladi.tistory.com/450#entry450comment</comments>
      <pubDate>Fri, 14 Feb 2025 16:12:23 +0900</pubDate>
    </item>
    <item>
      <title>Programmers SQL 고득점 Kit 풀기 - String, Date(2)</title>
      <link>https://fladi.tistory.com/449</link>
      <description>&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;이전 글&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://fladi.tistory.com/448&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://fladi.tistory.com/448&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;String이나 Date의 경우 함수명을 모르면 틀려야하기 때문에.. 복습이 중요할 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠르게 풀고 꾸준히 복습해야겠다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제풀이 사이트: &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/parts/17047&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/parts/17047&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;11. 루시와 엘라 찾기 - 레벨2&lt;/h2&gt;
&lt;pre id=&quot;code_1739387545469&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT ANIMAL_ID, NAME, SEX_UPON_INTAKE
FROM ANIMAL_INS 
WHERE NAME IN ('Lucy', 'Ella', 'Pickle', 'Rogan', 'Sabrina', 'Mitty')
ORDER BY ANIMAL_ID&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL에서는 배열을 소괄호로 표현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;12. 이름에 el이 들어가는 동물 찾기 - 레벨2&lt;/h2&gt;
&lt;pre id=&quot;code_1739387816595&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT ANIMAL_ID, NAME
FROM ANIMAL_INS 
WHERE NAME like '%el%' AND ANIMAL_TYPE = 'Dog'
ORDER BY NAME&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 이렇게 돌리면 맞았다고 나온다. 아마 mysql의 기본 collation인 utf8mb4_general_ci 같은 걸 사용했기 때문인 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 '%EL%'이든 '%eL%'이든 다 맞게 나올 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대소문자를 구분해서 검색하고 싶은 경우 다음과 같이 풀 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1739387986609&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT ANIMAL_ID, NAME
FROM ANIMAL_INS 
WHERE BINARY NAME like '%el%' AND ANIMAL_TYPE = 'Dog'
ORDER BY NAME&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;13. 중성화 여부 파악하기 - 레벨2&lt;/h2&gt;
&lt;pre id=&quot;code_1739388227857&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT ANIMAL_ID, NAME, 
    CASE
        WHEN (SEX_UPON_INTAKE like '%Neutered%' OR SEX_UPON_INTAKE like '%Spayed%') THEN 'O'
        ELSE 'X'
    END AS 중성화
FROM ANIMAL_INS 
ORDER BY ANIMAL_ID&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CASE WHEN THEN이나 IF를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;14. 오랜 기간 보호한 동물(2) - 레벨3&lt;/h2&gt;
&lt;pre id=&quot;code_1739388586738&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT OUTS.ANIMAL_ID, OUTS.NAME
FROM ANIMAL_OUTS OUTS
INNER JOIN ANIMAL_INS INS
ON OUTS.ANIMAL_ID = INS.ANIMAL_ID
ORDER BY (DATEDIFF(OUTS.DATETIME, INS.DATETIME)) DESC
LIMIT 2;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입양간 동물만 조회하기 위해 INNER JOIN을 먼저 수행해줬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2마리만 뽑아오기 위해 ORDER BY + LIMIT을 사용해줬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보호 기간을 구하기 위해 DATEDIFF를 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;15. 카테고리 별 상품 개수 구하기 - 레벨2&lt;/h2&gt;
&lt;pre id=&quot;code_1739388832195&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT SUBSTRING(PRODUCT_CODE, 1, 2) AS CATEGORY, COUNT(*) PRODUCTS
FROM PRODUCT 
GROUP BY CATEGORY
ORDER BY CATEGORY&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SUBSTRING 사용법과 그룹바이 사용법만 간단하게 알고있으면 어렵지않게 풀 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sql의 SBUSTRING은 시작 인덱스(1부터 시작)와 개수를 매개변수로 넣어줘야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;16. DATETIME에서 DATE로 형 변환 - 레벨2&lt;/h2&gt;
&lt;pre id=&quot;code_1739389088075&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT ANIMAL_ID, NAME, SUBSTRING(DATETIME, 1, 10) AS 날짜
FROM ANIMAL_INS 
ORDER BY ANIMAL_ID&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;17. 연도 별 평균 미세먼지 농도 조회하기 - 레벨2&lt;/h2&gt;
&lt;pre id=&quot;code_1739389376688&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;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 ASC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 그룹바이 잘 쓰면 된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;18. 한 해에 잡은 물고기 수 구하기 - 레벨1&lt;/h2&gt;
&lt;pre id=&quot;code_1739389484367&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT COUNT(*) AS FISH_COUNT
FROM FISH_INFO
WHERE YEAR(TIME) = 2021&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;19. 분기별 분화된 대장균의 개체 수 구하기 - 레벨2&lt;/h2&gt;
&lt;pre id=&quot;code_1739390200640&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT
    CONCAT(CEIL(MONTH(DIFFERENTIATION_DATE) / 3), 'Q') AS QUARTER, 
    COUNT(*) ECOLI_COUNT
FROM
    ECOLI_DATA
GROUP BY
    QUARTER
ORDER BY
    QUARTER

-- 123  1
-- 456  2
-- 789  3
-- 10,11,12 4&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 사람의 풀이를 보다 CEIL을 사용하는 걸 보고 나도 사용해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러가지 함수를 알아두면 유용할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>database</category>
      <author>fladi</author>
      <guid isPermaLink="true">https://fladi.tistory.com/449</guid>
      <comments>https://fladi.tistory.com/449#entry449comment</comments>
      <pubDate>Thu, 13 Feb 2025 04:19:55 +0900</pubDate>
    </item>
    <item>
      <title>Programmers SQL 고득점 Kit 풀기 - String, Date(1)</title>
      <link>https://fladi.tistory.com/448</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이전 글&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://fladi.tistory.com/447&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://fladi.tistory.com/447&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 IS NULL을 풀어봤다. 이번에는 String, Date를 빠르게 풀고 Group by와 JOIN으로 넘어가려고 한다. 사이트는 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/parts/17047&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/parts/17047&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 자동차 평균 대여 기간 구하기 - 레벨2&lt;/h2&gt;
&lt;pre id=&quot;code_1739352340026&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT CAR_ID, ROUND(AVG(DATEDIFF(END_DATE, START_DATE)+1), 1) AS AVERAGE_DURATION
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
GROUP BY CAR_ID
HAVING AVERAGE_DURATION &amp;gt;= 7
ORDER BY AVERAGE_DURATION DESC, CAR_ID DESC&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DATEDIFF라는 함수를 알아야한다. DATEDIFF는 차이 일수를 반환하는 함수다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대여일의 경우, 당일을 포함해야하기 때문에 (날짜2 - 날짜1 + 1) 로 계산해야한다. 그래서 +1을 해줬다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;GROUP BY와 AVG 집계함수를 통해 CAR_ID 그룹에 해당하는 평균점수를 구할 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;WHERE -&amp;gt; GROUP BY -&amp;gt; HAVING -&amp;gt; ORDER BY 순으로 쿼리가 실행되기 때문에, AVG 결과는 WHERE 조건절에 들어갈 수 없다. 그래서 HAVING에 AVERAGE_DURATION에 대한 조건을 적어줘야한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;집계함수의 결과를 필터링하기 위해 HAVING을 사용한다는 걸 꼭 기억하자!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #000000;&quot;&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #29261b; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 조회수가 가장 많은 중고거래 게시판의 첨부파일 조회하기 - 레벨3&lt;/h2&gt;
&lt;pre id=&quot;code_1739355669092&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT CONCAT('/home/grep/src/', BOARD_ID, '/', FILE_ID, FILE_NAME, FILE_EXT) AS FILE_PATH
FROM USED_GOODS_FILE A
WHERE BOARD_ID = (
    SELECT BOARD_ID
    FROM USED_GOODS_BOARD
    ORDER BY VIEWS DESC
    LIMIT 1
)
ORDER BY FILE_ID DESC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CONCAT만 알면 어렵지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1739356099955&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT CONCAT('/home/grep/src/', A.BOARD_ID, '/', FILE_ID, FILE_NAME, FILE_EXT) AS FILE_PATH
FROM USED_GOODS_FILE A
JOIN (
    SELECT BOARD_ID
    FROM USED_GOODS_BOARD
    ORDER BY VIEWS DESC
    LIMIT 1
) B 
ON A.BOARD_ID = B.BOARD_ID
ORDER BY FILE_ID DESC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JOIN을 사용할 수도 있따&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 조건에 부합하는 중고거래 상태 조회하기 - 레벨2&lt;/h2&gt;
&lt;pre id=&quot;code_1739356668879&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT BOARD_ID, WRITER_ID, TITLE, PRICE, 
    CASE 
        WHEN STATUS = 'SALE' THEN '판매중'
        WHEN STATUS = 'RESERVED' THEN '예약중'
        ELSE '거래완료'
    END AS STATUS
FROM USED_GOODS_BOARD 
WHERE CREATED_DATE = '2022-10-05'
ORDER BY BOARD_ID DESC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CASE WHEN THEN ELSE END 5개만 알면 풀 수 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 자동차 대여 기록 별 대여 금액 구하기 - 레벨4&lt;/h2&gt;
&lt;pre id=&quot;code_1739360186486&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT 
    CAR_HISTORY.HISTORY_ID, 
    ROUND(CAR_HISTORY.DAILY_FEE * CAR_HISTORY.DURATION * ((100 - IFNULL(PLAN.DISCOUNT_RATE, 0)) / 100))
    AS FEE
FROM 
(
    SELECT CAR.CAR_TYPE, CAR.DAILY_FEE, HISTORY.HISTORY_ID, DATEDIFF(END_DATE, START_DATE) + 1 as DURATION, 
        CASE
            WHEN DATEDIFF(END_DATE, START_DATE) + 1 &amp;gt;= 90 THEN '90일 이상'
            WHEN DATEDIFF(END_DATE, START_DATE) + 1 &amp;gt;= 30 THEN '30일 이상'
            WHEN DATEDIFF(END_DATE, START_DATE) + 1 &amp;gt;= 7 THEN '7일 이상'
            ELSE 'NONE'
        END AS DURATION_TYPE
    FROM CAR_RENTAL_COMPANY_CAR CAR
    JOIN CAR_RENTAL_COMPANY_RENTAL_HISTORY HISTORY
    ON CAR.CAR_ID = HISTORY.CAR_ID
    WHERE CAR.CAR_TYPE = '트럭'
) AS CAR_HISTORY
LEFT JOIN CAR_RENTAL_COMPANY_DISCOUNT_PLAN AS PLAN
ON CAR_HISTORY.CAR_TYPE = PLAN.CAR_TYPE
AND CAR_HISTORY.DURATION_TYPE = PLAN.DURATION_TYPE
ORDER BY FEE DESC, HISTORY_ID DESC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 복잡해서 어려웠다. 고민하다가 다른 사람의 풀이를 참고하였다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;car_id를 조건으로 car_rental_company_rental_history 테이블과 car_rental_company_car 테이블을 JOIN한다.&amp;nbsp;&amp;nbsp;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이 과정에서 DATEDIFF를 사용하여 사용한 기간을 미리 구한다&lt;/li&gt;
&lt;li&gt;DATEDIFF가 7일 이상인지, 30일 이상인지, 90일 이상인지 구해서 문자열로 저장해둔다&lt;/li&gt;
&lt;li&gt;type이 트럭인 애들만 나오도록 WHERE 조건을 추가한다&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;car_rental_company_discount_plan 테이블을 조인한다.&amp;nbsp;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;조건은 미리 구해둔 duration_type과 car_type을 사용한다. (JOIN의 경우 AND로 두 가지 조건을 사용할 수 있다)&lt;/li&gt;
&lt;li&gt;FEE는 미리 구한 사용한 기간과 하루 가격, 조건별 할인율을 포함하여 구한다&lt;/li&gt;
&lt;li&gt;조건별 할인율은 NULL일 수 있기 때문에 IFNULL을 사용하여 할인가격이 0이 되도록 처리한다&lt;/li&gt;
&lt;li&gt;(%가 붙어있어도 빼기가 돼서 신기했다. 안된다면 TRIM이나 SUBSTRING을 사용해야했겠다)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;ORDER BY를 사용하여 정렬하면 된다&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차근차근 풀면 어느정도 로직이 떠오르긴한다. 이런 복잡한 문제는 종이에 차근차근 쓰는 연습이 필요할 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 특정 옵션이 포함된 자동차 리스트 구하기 - 레벨1&lt;/h2&gt;
&lt;pre id=&quot;code_1739362170489&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT CAR_ID, CAR_TYPE, DAILY_FEE, OPTIONS
FROM CAR_RENTAL_COMPANY_CAR
WHERE OPTIONS like '%네비게이션%'
ORDER BY CAR_ID DESC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별 거 없다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 자동차 대여 기록에서 장기/단기 대여 구분하기 - 레벨1&lt;/h2&gt;
&lt;pre id=&quot;code_1739362444383&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT HISTORY_ID, CAR_ID, 
    DATE_FORMAT(START_DATE, '%Y-%m-%d'), DATE_FORMAT(END_DATE, '%Y-%m-%d'), 
    CASE 
        WHEN DATEDIFF(END_DATE, START_DATE) + 1 &amp;gt;= 30 THEN '장기 대여'
        ELSE '단기 대여'
    END AS RENT_TYPE
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY 
WHERE START_DATE like '2022-09%'
ORDER BY HISTORY_ID DESC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DATE_FORMAT 형식을 까먹어서 찍어가면서 맞췄다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Y m d를 기억하자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 조건별로 분류하여 주문상태 출력하기 - 레벨3&lt;/h2&gt;
&lt;pre id=&quot;code_1739362753481&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT ORDER_ID, PRODUCT_ID, 
    DATE_FORMAT(OUT_DATE, &quot;%Y-%m-%d&quot;), 
    CASE
        WHEN OUT_DATE IS NULL THEN '출고미정'
        WHEN DATEDIFF('2022-05-01', OUT_DATE) &amp;gt;= 0 THEN '출고완료'
        ELSE '출고대기'
    END AS 출고여부
FROM FOOD_ORDER
ORDER BY ORDER_ID ASC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DATE_FORMAT과 DATEDIFF만 알면 어렵지 않다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 대여 기록이 존재하는 자동차 리스트 구하기 - 레벨3&lt;/h2&gt;
&lt;pre id=&quot;code_1739363206300&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT DISTINCT H.CAR_ID AS CAR_ID
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY H
JOIN CAR_RENTAL_COMPANY_CAR C
ON H.CAR_ID = C.CAR_ID
WHERE MONTH(H.START_DATE) = 10 AND C.CAR_TYPE = '세단'
ORDER BY CAR_ID DESC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MONTH 함수를 알면 어렵지 않다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;like를 써도 풀릴 것 같긴하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 조건에 맞는 사용자 정보 조회하기 - 레벨3&lt;/h2&gt;
&lt;pre id=&quot;code_1739363659969&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT USER_ID, NICKNAME, 
    CONCAT(CITY, ' ', 	STREET_ADDRESS1, ' ', STREET_ADDRESS2) AS 전체주소,
    CONCAT(SUBSTRING(TLNO, 1, 3), '-', SUBSTRING(TLNO, 4, 4), '-', SUBSTRING(TLNO, 8, 4))
    AS 전화번호
FROM USED_GOODS_USER U
WHERE USER_ID IN (
    SELECT WRITER_ID
    FROM USED_GOODS_BOARD 
    GROUP BY WRITER_ID
    HAVING COUNT(WRITER_ID) &amp;gt;= 3
)
ORDER BY USER_ID DESC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LENGTH 함수를 찍어서 맞췄다. 꼭 잘 외우고 있어야겠다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SUBSTRING의 3번째 매개변수는 개수인 것 같았다. 프로그래밍 언어들과 조금 달라서 사용법을 외워둬야겠다고 생각했다. 1부터 시작하는 것도 유의해야할 사항이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JOIN과 GROUP BY를 써도 되지만 GROUP BY가 익숙하지 않아서 서브쿼리를 사용하였다. JOIN을 사용한 풀이는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1739363953921&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT USER_ID, NICKNAME, 
    CONCAT(CITY, ' ', 	STREET_ADDRESS1, ' ', STREET_ADDRESS2) AS 전체주소,
    CONCAT(SUBSTRING(TLNO, 1, 3), '-', SUBSTRING(TLNO, 4, 4), '-', SUBSTRING(TLNO, 8, 4))
    AS 전화번호
FROM USED_GOODS_USER U
JOIN USED_GOODS_BOARD B
ON U.USER_ID = B.WRITER_ID
GROUP BY USER_ID
HAVING COUNT(WRITER_ID) &amp;gt;= 3
ORDER BY USER_ID DESC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;10. 취소되지 않은 진료 예약 조회하기 - 레벨4&lt;/h2&gt;
&lt;pre id=&quot;code_1739387345480&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;SELECT AD.APNT_NO, P.PT_NAME, P.PT_NO, AD.MCDP_CD, AD.DR_NAME, AD.APNT_YMD as APNT_YMD
FROM (
    SELECT A.APNT_NO, D.MCDP_CD, D.DR_NAME, A.APNT_YMD, A.PT_NO
    FROM APPOINTMENT A
    LEFT JOIN DOCTOR D
    ON A.MDDR_ID = D.DR_ID
    WHERE A.MCDP_CD = 'CS'
        AND APNT_CNCL_YN = 'N'
        AND YEAR(APNT_YMD) = 2022
        AND MONTH(APNT_YMD) = 4
        AND DAY(APNT_YMD) = 13
) AD
LEFT JOIN PATIENT P
ON AD.PT_NO = P.PT_NO
ORDER BY APNT_YMD;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;APPOINTMENT와&lt;span&gt;&amp;nbsp;&lt;/span&gt;DOCTOR를 먼저 조인하면서 필요조건을 걸러내고, PATIENT를 JOIN해줬다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2022-04-13에 해당하는 것을 like로 찾으려다가 YEAR, MONTH, DAY 함수를 사용해봤다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나는 서브쿼리를 사용했는데, 3조인으로 해도 충분히 가능한 문제다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>database</category>
      <author>fladi</author>
      <guid isPermaLink="true">https://fladi.tistory.com/448</guid>
      <comments>https://fladi.tistory.com/448#entry448comment</comments>
      <pubDate>Wed, 12 Feb 2025 21:36:24 +0900</pubDate>
    </item>
    <item>
      <title>Programmers SQL 고득점 Kit 풀기 - IS NULL</title>
      <link>https://fladi.tistory.com/447</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이전 글&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://fladi.tistory.com/446&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://fladi.tistory.com/446&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 SUM,MIN,MAX 집계함수에 대해 알아봤다. ISNULL 부분도 빠르게 풀어보려고 한다. 문제 사이트는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/parts/17045&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/parts/17045&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 경기도에 위치한 식품창고 목록 출력하기 - 레벨1&lt;/h2&gt;
&lt;pre id=&quot;code_1739279307979&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT WAREHOUSE_ID, WAREHOUSE_NAME, ADDRESS, IFNULL(FREEZER_YN, 'N') as FREEZER_YN
FROM FOOD_WAREHOUSE
WHERE ADDRESS like '%경기도%'
ORDER BY WAREHOUSE_ID&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IFNULL이라는 함수를 모르면 못푸는 문제다. 알면 쉽게 풀 수 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 이름이 없는 동물의 아이디&lt;span&gt;&amp;nbsp;&lt;/span&gt;- 레벨1&lt;/h2&gt;
&lt;pre id=&quot;code_1739279440236&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT ANIMAL_ID 
FROM ANIMAL_INS
WHERE NAME IS NULL&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방심하다가 == NULL 이런거만 안쓰면 틀리지 않는 문제다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 이름이 있는 동물의 아이디&lt;span&gt;&amp;nbsp;&lt;/span&gt;- 레벨1&lt;/h2&gt;
&lt;pre id=&quot;code_1739279516033&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT ANIMAL_ID 
FROM ANIMAL_INS
WHERE NAME IS NOT NULL
ORDER BY ANIMAL_ID&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;!= NULL 이런거만 안쓰면 된다. IS NOT NULL만 알면 풀 수 있는 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. NULL 처리하기&lt;span&gt;&amp;nbsp;&lt;/span&gt;- 레벨2&lt;/h2&gt;
&lt;pre id=&quot;code_1739285828985&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT ANIMAL_TYPE, IFNULL(NAME, 'No name'), SEX_UPON_INTAKE
FROM ANIMAL_INS 
ORDER BY ANIMAL_ID&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;IFNULL만 알면 되는 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 나이 정보가 없는 회원 수 구하기&lt;span&gt;&amp;nbsp;&lt;/span&gt;- 레벨1&lt;/h2&gt;
&lt;pre id=&quot;code_1739285915579&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT COUNT(*) as USERS
FROM USER_INFO
WHERE AGE IS NULL&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. ROOT 아이템 구하기&lt;span&gt;&amp;nbsp;&lt;/span&gt;- 레벨2&lt;/h2&gt;
&lt;pre id=&quot;code_1739286091666&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT INFO.ITEM_ID, INFO.ITEM_NAME
FROM ITEM_TREE TREE LEFT JOIN ITEM_INFO INFO
ON  TREE.ITEM_ID = INFO.ITEM_ID
WHERE TREE.PARENT_ITEM_ID IS NULL&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 업그레이드 할 수 없는 아이템 구하기&lt;span&gt;&amp;nbsp;&lt;/span&gt;- 레벨3&lt;/h2&gt;
&lt;pre id=&quot;code_1739339923196&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT ITEM_ID, ITEM_NAME, RARITY
FROM ITEM_INFO
WHERE ITEM_ID NOT IN
(
    SELECT DISTINCT PARENT_ITEM_ID 
    FROM ITEM_TREE
    WHERE PARENT_ITEM_ID IS NOT NULL    
)
ORDER BY ITEM_ID DESC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자신이 PARENT_ITEM_ID인 아이템이 하나도 없으면 최종 업그레이드 아이템이다. 천천히 종이에 쓰면서 계산하면 쉽게 생각할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자식이 있는 아이템을 조회한다. (PAERNT_ITEM_ID로 포함되는 애들을 찾는다. DISTINCT로 중복도 제거해줬다)&lt;/li&gt;
&lt;li&gt;자식이 있는 ITEM_ID가 아닌 아이템 정보를 조회하여 출력한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 잡은 물고기의 평균 길이 구하기 - 레벨1&lt;/h2&gt;
&lt;pre id=&quot;code_1739343723909&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT ROUND(AVG(IFNULL(LENGTH, 10)), 2) AVERAGE_LENGTH
FROM FISH_INFO&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ROUND, AVG, IFNULL을 알면 쉬운 문제다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>database</category>
      <author>fladi</author>
      <guid isPermaLink="true">https://fladi.tistory.com/447</guid>
      <comments>https://fladi.tistory.com/447#entry447comment</comments>
      <pubDate>Wed, 12 Feb 2025 00:02:45 +0900</pubDate>
    </item>
    <item>
      <title>Programmers SQL 고득점 Kit 풀기 - SUM,MAX,MIN</title>
      <link>https://fladi.tistory.com/446</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;이전 글&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/parts/17043&quot;&gt;https://school.programmers.co.kr/learn/courses/30/parts/17043&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 SELECT를 풀었으니, 이제는 SUM,MAX,MIN을 풀어보려고 한다. 문제 수가 얼마 없어서 빠르게 풀 수 있을 것 같다. 문제사이트는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/parts/17043&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/parts/17043&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 가격이 제일 비싼 식품의 정보 출력하기 - 레벨1&lt;/h2&gt;
&lt;pre id=&quot;code_1739273273827&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT PRODUCT_ID, PRODUCT_NAME, PRODUCT_CD, CATEGORY, PRICE
FROM FOOD_PRODUCT
ORDER BY PRICE DESC
LIMIT 1;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ORDER BY와 LIMIT을 쓰면 이렇게 풀 수 있다. MAX를 이용한 풀이는 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1739273366478&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT PRODUCT_ID, PRODUCT_NAME, PRODUCT_CD, CATEGORY, PRICE
FROM FOOD_PRODUCT
WHERE PRICE = (SELECT MAX(PRICE) FROM FOOD_PRODUCT)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 쿼리는 테이블은 한 번만 스캔하지만, 정렬작업이 필요하다. 물론 LIMIT 1로 인해 전체 정렬이 필요하지 않을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 쿼리는 MAX를 구하기 위해 전체 스캔과정과, 만족하는 행을 찾기 위한 스캔 총 2번의 스캔이 필요하다. 최대 가격이 동일한 상품이 여러 개인 경우 빠르게 찾아올 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 쿼리 모두&amp;nbsp; PRICE 컬럼에 인덱스가 있는 경우 이를 활용해 빠르게 조회가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 가장 비싼 상품 구하기 - 레벨1&lt;/h2&gt;
&lt;pre id=&quot;code_1739273803050&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT MAX(PRICE) as MAX_PRICE
FROM PRODUCT;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설명이 별로 필요없다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 최댓값 구하기 - 레벨1&lt;/h2&gt;
&lt;pre id=&quot;code_1739274037834&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT MAX(DATETIME) as 시간
FROM ANIMAL_INS&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 최솟값 구하기 - 레벨2&lt;/h2&gt;
&lt;pre id=&quot;code_1739274071778&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT MIN(DATETIME) as 시간
FROM ANIMAL_INS&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 쉽다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 동물 수 구하기&amp;nbsp; - 레벨2&lt;/h2&gt;
&lt;pre id=&quot;code_1739274147500&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT COUNT(*)
FROM ANIMAL_INS&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 중복 제거하기 - 레벨2&lt;/h2&gt;
&lt;pre id=&quot;code_1739274484269&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT COUNT(DISTINCT NAME) as count
FROM ANIMAL_INS&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DISTINCT를 사용하는 문제이다. NAME이 중복되지 않도록 SELECT를 하고, 해당 NAME들을 count 집계함수로 씌워주면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 조건에 맞는 아이템들의 가격의 총합 구하기 - 레벨2&lt;/h2&gt;
&lt;pre id=&quot;code_1739274594354&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT SUM(PRICE) as TOTAL_PRICE 
FROM ITEM_INFO 
WHERE RARITY = &quot;LEGEND&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SUM 함수만 알면 되는 문제다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 물고기 종류 별 대어 찾기 - 레벨3&lt;/h2&gt;
&lt;pre id=&quot;code_1739275001681&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT fi.ID, fni.FISH_NAME, fi.LENGTH
FROM fish_info as fi
JOIN fish_name_info as fni
ON fi.fish_type = fni.fish_type
WHERE (fi.fish_type, fi.length) IN ( 
    SELECT fish_type, MAX(length)
    FROM fish_info
    GROUP BY fish_type
)
ORDER BY fi.ID;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;group by를 잘 몰라서 잘하는 친구의 코드를 참고했다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;FISH_TYPE 별로 GROUP BY를 수행하여 최대 길이를 가지는 (FISH_TYPE, LENGTH)의 쌍을 구한다&lt;/li&gt;
&lt;li&gt;해당 FISH_TYPE과 LENGTH를 가지는 애를 fish_info 테이블에서 찾아낸다. 어차피 가장 큰 물고기는 한 마리밖에 없기 때문에 그냥 찾아내면 된다.&lt;/li&gt;
&lt;li&gt;fish_name_info에 JOIN한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 잡은 물고기 중 가장 큰 물고기의 길이 구하기 - 레벨1&lt;/h2&gt;
&lt;pre id=&quot;code_1739277397249&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT CONCAT(MAX(LENGTH), &quot;cm&quot;) as MAX_LENGTH
FROM FISH_INFO&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수명을 대충 때려맞췄는데 맞았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. 연도별 대장균 크기의 편차 구하기&amp;nbsp; - 레벨2&lt;/h2&gt;
&lt;pre id=&quot;code_1739277767503&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT YEAR(DIFFERENTIATION_DATE) AS YEAR,
(
    SELECT MAX(SIZE_OF_COLONY) FROM ECOLI_DATA
    WHERE YEAR(DIFFERENTIATION_DATE) = YEAR
) - SIZE_OF_COLONY AS YEAR_DEV,
ID
FROM ECOLI_DATA
ORDER BY YEAR, YEAR_DEV&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 제대로 읽지 않아서 틀렸다.&amp;nbsp;연도 별 가장 큰 값을조회해서 현재 값을 뺀 값을 YEAR_DEV로 넣어주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 모든 튜플마다 해당 YEAR에 맞는 최대 SIZE를 찾아내기 때문에 비효율적일 수 있다. 윈도우 함수를 사용한다면 PARTITION BY를 통해 지정된 그룹별로 계산을 한 번에 수행이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1739278010396&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT 
    YEAR(DIFFERENTIATION_DATE) AS YEAR,
    MAX(SIZE_OF_COLONY) OVER (PARTITION BY YEAR(DIFFERENTIATION_DATE)) - SIZE_OF_COLONY AS YEAR_DEV,   
    ID
FROM ECOLI_DATA
ORDER BY YEAR, YEAR_DEV&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;데이터를 YEAR(DIFFERENTIATION_DATE) 별로 파티션을 나눈다&lt;/li&gt;
&lt;li&gt;각 파티션 내에서 MAX(SIZE_OF_COLONY) 값을 한 번에 계산한다&lt;/li&gt;
&lt;li&gt;계산된 MAX 값을 해당 파티션의 모든 행에 적용한다&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브쿼리를 써도 시간 초과가 나지 않고 통과하지만, 실제 서비스를 운영한다면 윈도우 함수를 쓰는 게 효율적이겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>database</category>
      <author>fladi</author>
      <guid isPermaLink="true">https://fladi.tistory.com/446</guid>
      <comments>https://fladi.tistory.com/446#entry446comment</comments>
      <pubDate>Tue, 11 Feb 2025 21:48:34 +0900</pubDate>
    </item>
    <item>
      <title>[JAVA] 백준 7579 앱 - 골드3</title>
      <link>https://fladi.tistory.com/445</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;477&quot; data-origin-height=&quot;322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cs9B5y/btsMcqSarMI/tOcFRKLV1kJFOkKQnf4mdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cs9B5y/btsMcqSarMI/tOcFRKLV1kJFOkKQnf4mdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cs9B5y/btsMcqSarMI/tOcFRKLV1kJFOkKQnf4mdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcs9B5y%2FbtsMcqSarMI%2FtOcFRKLV1kJFOkKQnf4mdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;477&quot; height=&quot;322&quot; data-origin-width=&quot;477&quot; data-origin-height=&quot;322&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주절거리는 서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웬만하면 문제 하나를 티스토리 게시글로 올리지 않는데, 너무 재밌는 문제라서 올려본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dp를 어느정도 풀어봤다고 생각했는데, 이런 유형은 처음 접해서 너무 참신했다. dp를 조금 더 공부하고싶다는 생각이 들었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 링크&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/7579&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/7579&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YlcwN/btsMbWqv8r7/u4Wif8az8RpxoIkfSVwgik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YlcwN/btsMbWqv8r7/u4Wif8az8RpxoIkfSVwgik/img.png&quot; data-alt=&quot;2025-02-09 기준 푼 사람이 많다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YlcwN/btsMbWqv8r7/u4Wif8az8RpxoIkfSVwgik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYlcwN%2FbtsMbWqv8r7%2Fu4Wif8az8RpxoIkfSVwgik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;684&quot; height=&quot;170&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2025-02-09 기준 푼 사람이 많다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;풀이 접근법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 dp 풀이로 들어가면 무조건 메모리가 터진다. 예를 들어, 필요한 바이트 수인 M을 구하기 위해 dp 배열을 만든다고 하면, 최대 100 * 10_000_000 = 10억 크기의 배열이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제는 조금 변형해서 생각해야한다. 접근법은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;특정 비용일 때 얻을 수 있는 최대 바이트 수를 dp배열에 저장&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;u&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;비용&lt;/span&gt;&lt;/b&gt;&lt;/u&gt;을 dp배열의 열로 만들고, dp배열의 값은 얻을 수 있는 &lt;u&gt;&lt;b&gt;최대 바이트 수를 저장&lt;/b&gt;&lt;/u&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제에서 비용의 경우 최대 100 * 100 = 10000 개밖에 되지 않기 때문에 충분히 배열로 만들 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;특정 앱을 중지시킨다고 가정했을 때, 특정 비용일 때 얻을 수 있는 최대 바이트수를 저장&lt;/li&gt;
&lt;li&gt;비용을 구하다 원하는 비용을 찾는다면, 그 이후 비용은 고려하지 않아도 됨 -&amp;gt; minCostPlusOne 값에 저장하여 시간절약&lt;/li&gt;
&lt;li&gt;이후에는 비용 0 부터 minCostPlusOne 이전까지만 확인하여 최소 비용을 찾아냄&lt;/li&gt;
&lt;li&gt;정답은 minCostPlusOne-1&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(minCostPlusOne 값을 +1해서 저장한 이유는 for문에 &amp;lt;=(등호)를 쓰기 싫어서이다. 큰 이유는 없다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드&lt;/h2&gt;
&lt;pre id=&quot;code_1739099399920&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Main {
    static int[][] value;

    public static void main(String[] args) throws IOException {
    	//입력받기 -----------------
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        int appCnt = Integer.parseInt(st.nextToken());
        int requiredByte = Integer.parseInt(st.nextToken());

        value = new int[appCnt][2];
        st = new StringTokenizer(br.readLine());
        for (int i = 0; i &amp;lt; appCnt; i++) {
            value[i][0] = Integer.parseInt(st.nextToken()); //바이트 수
        }

        st = new StringTokenizer(br.readLine());
        for (int i = 0; i &amp;lt; appCnt; i++) {
            value[i][1] = Integer.parseInt(st.nextToken()); //비용
        }
        //입력 끝 -----------------

		//선택할 앱을 행으로, 비용을 열로 잡음(나올 수 있는 최대 비용을 계산)
        int[][] dp = new int[appCnt + 1][appCnt * 100 + 1];
        
        //최소비용 + 1 값이 저장되는 변수
        int minCostPlusOne = dp[0].length;

        for (int i = 1; i &amp;lt; dp.length; i++) {
            int byteCnt = value[i - 1][0];
            int cost = value[i - 1][1];

            for (int j =0; j&amp;lt;minCostPlusOne; j++) {
                if (j - cost &amp;lt; 0) {
                    dp[i][j] = dp[i-1][j];
                    continue;
                }

                if (dp[i-1][j] &amp;lt; dp[i-1][j-cost] + byteCnt) {
                    dp[i][j] = dp[i-1][j-cost] + byteCnt;
					
                    //최적화를 위한 코드. 필요 바이트수를 넘어갔다면 이후 비용은 볼 필요가 없다
                    if (dp[i][j] &amp;gt;= requiredByte) {
                        minCostPlusOne = j+1;
                    }
                } else {
                    dp[i][j] = dp[i-1][j];
                }
            }
        }

        System.out.println(minCostPlusOne-1);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;제출 결과&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;55&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TzfMd/btsMdlJv6mY/nD1jSUzHYYKTK9GQPDX9I1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TzfMd/btsMdlJv6mY/nD1jSUzHYYKTK9GQPDX9I1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TzfMd/btsMdlJv6mY/nD1jSUzHYYKTK9GQPDX9I1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTzfMd%2FbtsMdlJv6mY%2FnD1jSUzHYYKTK9GQPDX9I1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;726&quot; height=&quot;55&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;55&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 재밌었따~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고한 블로그&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혼자서는 못풀어서 다음 블로그를 참고했다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dragon-h.tistory.com/9&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dragon-h.tistory.com/9&lt;/a&gt;&lt;/p&gt;</description>
      <category>알고리즘/백준</category>
      <author>fladi</author>
      <guid isPermaLink="true">https://fladi.tistory.com/445</guid>
      <comments>https://fladi.tistory.com/445#entry445comment</comments>
      <pubDate>Sun, 9 Feb 2025 20:24:59 +0900</pubDate>
    </item>
  </channel>
</rss>