<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>꼬부기의 코딩수련소</title>
    <link>https://kkobug2.tistory.com/</link>
    <description>꼬부기송이가 거북왕이 될때까지 천천히 진화하는 코딩공부

mail : seohwa56@naver.com</description>
    <language>ko</language>
    <pubDate>Thu, 25 Jun 2026 20:27:13 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>서화</managingEditor>
    <image>
      <title>꼬부기의 코딩수련소</title>
      <url>https://tistory1.daumcdn.net/tistory/8342678/attach/7695094c32c246658ff4e43408a4169b</url>
      <link>https://kkobug2.tistory.com</link>
    </image>
    <item>
      <title>TOP-N 쿼리</title>
      <link>https://kkobug2.tistory.com/270</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;TOP-N 쿼리란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TOP-N 쿼리는 SQL에서 &lt;b&gt;상위 N개 행&lt;/b&gt;을 추출하는 방법을 말한다.&lt;br /&gt;특정 기준으로 가장 높은 값이나 가장 낮은 값을 가진 데이터를 일정 개수만 조회할 때 사용하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)점수가 가장 높은 학생 상위 3명, 매출이 가장 높은 상품 5개&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ROWNUM&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ROWNUM은 Oracle 데이터베이스에서 조회 결과에 대해 임시로 부여되는 순차 번호다.&lt;br /&gt;조회된 행의 순서대로 1부터 번호가 매겨지며, 이 값은 테이블에 실제로 저장된 컬럼이 아니라 조회 시점에만 만들어지는 가상 컬럼이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 가상 컬럼 예시로 ROWNUM, ROWID, LEVEL 등이 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;pre id=&quot;code_1776252544842&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT ROWNUM, STUDENT_ID, NAME, SCORE
FROM STUDENT_SCORE;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과에는 조회된 순서대로 1, 2, 3... 같은 번호가 붙는다.&lt;br /&gt;여기서 중요한 점은 테이블 저장 순서가 아니라 조회 결과 순서에 따라 ROWNUM이 부여된다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; WHERE절에서 ROWNUM 사용하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ROWNUM은 WHERE절에서 사용할 수 있다.&lt;br /&gt;특정 개수 이하의 행만 가져오고 싶을 때 유용하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시) 상위 3개만 결과 보기&lt;/p&gt;
&lt;pre id=&quot;code_1776252630633&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT ROWNUM, STUDENT_ID, NAME, SCORE
FROM STUDENT_SCORE
WHERE ROWNUM &amp;lt;= 3;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조회 결과에 부여된 ROWNUM 기준으로 1, 2, 3까지만 출력한다.&lt;br /&gt;따라서 앞에서부터 3개만 가져오기 라고 이해하면 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ROWNUM 사용 시 주의사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;ROWNUM은 1부터 시작해서 순차적으로 증가해야 한다. 그래서 1이 먼저 선택되지 않으면 2나 3도 선택될 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘못된 예시 1)&lt;/p&gt;
&lt;pre id=&quot;code_1776252719414&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT *
FROM STUDENT_SCORE
WHERE ROWNUM = 2;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 쿼리는 아무 결과도 나오지 않는다.&lt;br /&gt;왜냐하면 ROWNUM은 1부터 부여되는데, 1이 먼저 선택되지 않은 상태에서는 2라는 값을 만들 수 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가능한 예시)&lt;/p&gt;
&lt;pre id=&quot;code_1776252770474&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT *
FROM STUDENT_SCORE
WHERE ROWNUM = 1;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 정상적으로 첫 번째 행이 출력된다.&lt;br /&gt;ROWNUM의 시작값이 1이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘못된 예시 2)&lt;/p&gt;
&lt;pre id=&quot;code_1776252921182&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT *
FROM STUDENT_SCORE
WHERE ROWNUM &amp;gt;= 2;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;역시 첫 번째 기준값인 1을 건너뛰었기 때문에 2 이상도 만들 수 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가능한 예시)&lt;/p&gt;
&lt;pre id=&quot;code_1776252966285&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT *
FROM STUDENT_SCORE
WHERE ROWNUM &amp;lt;= 3;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 가능하다.&lt;br /&gt;ROWNUM 1부터 시작해서 순서대로 2, 3까지 평가할 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리 : ROWNUM은 1부터 시작한다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ROWNUM만으로는 정확한 TOP-N이 안 되는 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 ROWNUM &amp;lt;= N만 쓴다고 해서 정렬된 기준의 상위 N개 가 되는 것은 아니다. Oracle에서는 ROWNUM 필터링이 ORDER BY보다 먼저 적용되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시) 점수가 가장 높은 3명을 구할때&lt;/p&gt;
&lt;pre id=&quot;code_1776253125495&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT STUDENT_ID, NAME, SCORE
FROM STUDENT_SCORE
WHERE ROWNUM &amp;lt;= 3
ORDER BY SCORE DESC;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 기대한것 : 점수 상위 3명&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로는 먼저 ROWNUM &amp;lt;= 3으로 앞의 3개 행이 잘리고, 그 잘린 결과 안에서만 ORDER BY가 적용된다.&lt;br /&gt;따라서 정렬된 전체 결과에서 상위 3개를 뽑는 것이 아니라, 앞에서 3개를 먼저 뽑고 그 3개를 정렬하는 셈이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 정확한 TOP-N이 보장되지 않는다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정확한 TOP-N을 구하는 방법: 인라인 뷰 + ROWNUM&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확한 TOP-N을 구하려면 먼저 정렬을 끝낸 뒤, 그 결과에 ROWNUM 조건을 걸어야 한다.&lt;br /&gt;이때 사용하는 방법이 인라인 뷰다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;pre id=&quot;code_1776253240843&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT STUDENT_ID, NAME, SCORE
FROM (
    SELECT STUDENT_ID, NAME, SCORE
    FROM STUDENT_SCORE
    ORDER BY SCORE DESC
)
WHERE ROWNUM &amp;lt;= 3;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;3500&quot; data-start=&quot;3307&quot; data-ke-size=&quot;size16&quot;&gt;이 쿼리는 먼저 인라인 뷰 안에서 SCORE 기준 내림차순 정렬을 완료한 다음 바깥쪽 WHERE절에서 ROWNUM &amp;lt;= 3을 적용한다.&lt;br /&gt;따라서 정렬된 결과에서 진짜 상위 3명만 남기게 된다.&lt;/p&gt;
&lt;p data-end=&quot;3607&quot; data-start=&quot;3502&quot; data-ke-size=&quot;size16&quot;&gt;정리 : Oracle에서 TOP-N을 정확하게 구현하려면 정렬 먼저, ROWNUM 나중 순서로 진행하면 된다&lt;/p&gt;
&lt;h3 data-end=&quot;3607&quot; data-start=&quot;3502&quot; data-ke-size=&quot;size23&quot;&gt;SQL Server의 TOP-N 구현 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL Server에서는 Oracle처럼 인라인 뷰와 ROWNUM을 조합할 필요 없이, TOP(N) 키워드를 사용해서 훨씬 간단하게 상위 N개를 조회할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Oracle 방식과 비교하면 SQL Server가 더 직관적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본구조&lt;/p&gt;
&lt;pre id=&quot;code_1776253376557&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT TOP (N) 컬럼목록
FROM 테이블명
ORDER BY 정렬기준;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;3941&quot; data-start=&quot;3913&quot; data-ke-size=&quot;size16&quot;&gt;TOP 구문의 주요 옵션&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4075&quot; data-start=&quot;3943&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3966&quot; data-start=&quot;3943&quot;&gt;TOP(N) : 상위 N개 행 반환&lt;/li&gt;
&lt;li data-end=&quot;3997&quot; data-start=&quot;3967&quot;&gt;PERCENT : 상위 N개가 아니라 N% 반환&lt;/li&gt;
&lt;li data-end=&quot;4075&quot; data-start=&quot;3998&quot;&gt;WITH TIES : 마지막 값과 동일한 데이터를 함께 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시) 점수가 가장 높은 상위 3명 구하기&lt;/p&gt;
&lt;pre id=&quot;code_1776253430795&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT TOP (3) STUDENT_ID, NAME, SCORE
FROM STUDENT_SCORE
ORDER BY SCORE DESC;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리문을 이렇게 작성하면 상위 3개를 바로 가져올 수 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; WITH TIES 옵션&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬 기준의 마지막 값과 동일한 데이터가 있을 경우 함께 출력하는 옵션이다.&lt;br /&gt;딱 N개만 자르는 것이 아니라, 마지막 순위에서 동점이 있다면 그 데이터도 추가로 포함시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의할 점은 반드시 ORDER BY와 함께 사용해야 한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;pre id=&quot;code_1776254286303&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT TOP (3) WITH TIES STUDENT_ID, NAME, SCORE
FROM STUDENT_SCORE
ORDER BY SCORE DESC;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3등 점수가 90점인데, 다른 학생도 같은 90점이라면 그 학생도 함께 결과에 포함된다.&lt;br /&gt;TOP-N에서 동점 처리까지 같이 하고 싶을 때 유용한 옵션이다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Oracle의 FETCH 문&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Oracle 12c부터는 복잡한 인라인 뷰 없이도 FETCH 문으로 TOP-N을 간단하게 처리할 수 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에는 ROWNUM + 인라인 뷰 조합이 필요했지만, 최신 Oracle에서는 FETCH 구문으로 더 쉽게 쓸 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본구조&lt;/p&gt;
&lt;pre id=&quot;code_1776254368349&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[OFFSET offset ROWS]
[FETCH { FIRST | NEXT } { rowcount | percent PERCENT } ROWS { ONLY | WITH TIES }]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;5143&quot; data-start=&quot;5119&quot; data-ke-size=&quot;size16&quot;&gt;주요 키워드&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;5343&quot; data-start=&quot;5145&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;5176&quot; data-start=&quot;5145&quot;&gt;OFFSET : 앞에서 몇 개 행을 건너뛸지 지정&lt;/li&gt;
&lt;li data-end=&quot;5205&quot; data-start=&quot;5177&quot;&gt;FETCH : 가져올 행 수 또는 비율 지정&lt;/li&gt;
&lt;li data-end=&quot;5242&quot; data-start=&quot;5206&quot;&gt;FIRST, NEXT : 앞에서부터 지정 개수 가져오기&lt;/li&gt;
&lt;li data-end=&quot;5267&quot; data-start=&quot;5243&quot;&gt;ONLY : 정확히 지정한 수만 반환&lt;/li&gt;
&lt;li data-end=&quot;5343&quot; data-start=&quot;5268&quot;&gt;WITH TIES : 마지막 값과 동률인 행도 함께 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; FETCH 문 예시&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 1) 상위 3명 조회&lt;/p&gt;
&lt;pre id=&quot;code_1776254431109&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT STUDENT_ID, NAME, SCORE
FROM STUDENT_SCORE
ORDER BY SCORE DESC
FETCH FIRST 3 ROWS ONLY;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 쿼리는 SCORE 기준으로 내림차순 정렬한 뒤, 앞에서 3개 행만 가져온다.&lt;br /&gt;따라서 Oracle에서도 SQL Server의 TOP처럼 간단하게 상위 N개를 조회할 수 있게 된 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 2) 상위 3명을 건너뛰고 다음 2명 조회&lt;/p&gt;
&lt;pre id=&quot;code_1776254474598&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT STUDENT_ID, NAME, SCORE
FROM STUDENT_SCORE
ORDER BY SCORE DESC
OFFSET 3 ROWS FETCH NEXT 2 ROWS ONLY;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 쿼리는 먼저 SCORE 기준 내림차순 정렬을 하고, 앞의 3행을 건너뛴 다음, 그다음 2행만 반환한다.&lt;br /&gt;OFFSET은 건너뛰기, FETCH NEXT는 가져오기 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category> 꼬부기의 성장서재/이기적SQLD 기출문제</category>
      <author>서화</author>
      <guid isPermaLink="true">https://kkobug2.tistory.com/270</guid>
      <comments>https://kkobug2.tistory.com/270#entry270comment</comments>
      <pubDate>Wed, 15 Apr 2026 20:21:39 +0900</pubDate>
    </item>
    <item>
      <title>윈도우 함수</title>
      <link>https://kkobug2.tistory.com/265</link>
      <description>&lt;h3 data-end=&quot;99&quot; data-start=&quot;86&quot; data-ke-size=&quot;size23&quot;&gt;1. 윈도우 함수란&lt;/h3&gt;
&lt;p data-end=&quot;360&quot; data-start=&quot;101&quot; data-ke-size=&quot;size16&quot;&gt;윈도우 함수는 테이블의 행을 유지한 상태에서 순위, 누적합, 이전값&amp;middot;다음값 같은 값을 계산하는 함수다.&lt;br /&gt;GROUP BY처럼 여러 행을 하나로 줄여버리는 함수가 아니라, &lt;b&gt;원본 행을 그대로 남겨두면서 각 행 옆에 계산 결과를 추가하는 함수&lt;/b&gt;라고 볼수 있다&lt;/p&gt;
&lt;p data-end=&quot;360&quot; data-start=&quot;101&quot; data-ke-size=&quot;size16&quot;&gt;윈도우 함수의 큰 분류로는 순위 함수, 그룹 내 집계 함수, 그룹 내 행 순서 함수, 그룹 내 비율 함수로 정리할수 있다&lt;/p&gt;
&lt;h3 data-end=&quot;643&quot; data-start=&quot;625&quot; data-ke-size=&quot;size23&quot;&gt;2. 윈도우 함수 기본 문법&lt;/h3&gt;
&lt;p data-end=&quot;792&quot; data-start=&quot;645&quot; data-ke-size=&quot;size16&quot;&gt;윈도우 함수는 반드시 OVER()와 함께 사용해야 한다.&lt;br /&gt;함수 옆에 OVER()가 없다면 윈도우 함수가 아니다. OVER() 안에는 다음 요소가 들어갈 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1775835577671&quot; class=&quot;pgsql&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;WINDOW_FUNCTION() OVER (
[PARTITION BY 컬럼]
[ORDER BY 컬럼]
[WINDOWING 절]
)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;937&quot; data-start=&quot;794&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;823&quot; data-start=&quot;794&quot;&gt;PARTITION BY : 그룹을 나누는 기준&lt;/li&gt;
&lt;li data-end=&quot;855&quot; data-start=&quot;824&quot;&gt;ORDER BY : 각 그룹 안에서 정렬하는 기준&lt;/li&gt;
&lt;li data-end=&quot;937&quot; data-start=&quot;856&quot;&gt;WINDOWING : 현재 행을 기준으로 어디까지 계산할지 정하는 범위&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1034&quot; data-start=&quot;939&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;어떤 함수를 쓸지 정하고, OVER() 안에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;그룹 기준&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;정렬 기준&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;계산 범위&lt;/b&gt;를 정하는 방식이다.&lt;/p&gt;
&lt;p data-end=&quot;1034&quot; data-start=&quot;939&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;즉 윈도우 함수는 단순 계산 함수가 아니라,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;행의 맥락을 보고 계산하는 함수&lt;/b&gt;라고 이해하면 된다.&lt;/p&gt;
&lt;h3 data-end=&quot;1057&quot; data-start=&quot;1041&quot; data-ke-size=&quot;size23&quot;&gt;3. 그룹 내 순위 함수&lt;/h3&gt;
&lt;p data-end=&quot;1191&quot; data-start=&quot;1059&quot; data-ke-size=&quot;size16&quot;&gt;그룹 내 순위 함수로 RANK, DENSE_RANK, ROW_NUMBER가 있다&lt;br /&gt;이 함수들은 비슷해 보이지만 동점 처리 방식이 서로 다르다.&lt;/p&gt;
&lt;h3 data-end=&quot;1208&quot; data-start=&quot;1193&quot; data-ke-size=&quot;size23&quot;&gt;RANK()&lt;/h3&gt;
&lt;p data-end=&quot;1395&quot; data-start=&quot;1210&quot; data-ke-size=&quot;size16&quot;&gt;RANK는 정렬 기준에 따라 순위를 매기는데, &lt;b&gt;동점이면 같은 순위를 부여하고 다음 순위를 건너뛴다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-end=&quot;1395&quot; data-start=&quot;1210&quot; data-ke-size=&quot;size16&quot;&gt;예시) &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;매출이 같은 두 상품이 2등이면, 그다음 순위는 3등이 아니라 4등으로 표시하기&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;pre id=&quot;code_1775835732664&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT BRANCH_ID, PRODUCT_NAME, SALES_AMOUNT,
RANK() OVER (ORDER BY SALES_AMOUNT DESC) AS RANKING
FROM SALES_DATA;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1614&quot; data-start=&quot;1543&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1&amp;rarr;2=2 &amp;rarr; 4&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;1614&quot; data-start=&quot;1543&quot; data-ke-size=&quot;size16&quot;&gt;따라서 RANK는 &lt;b&gt;경쟁 순위&lt;/b&gt;처럼 생각하면 이해하기 쉽다.&lt;/p&gt;
&lt;h3 data-end=&quot;1637&quot; data-start=&quot;1616&quot; data-ke-size=&quot;size23&quot;&gt;DENSE_RANK()&lt;/h3&gt;
&lt;p data-end=&quot;1837&quot; data-start=&quot;1639&quot; data-ke-size=&quot;size16&quot;&gt;DENSE_RANK도 동점이면 같은 순위를 부여한다.&lt;br /&gt;다만 RANK와 다른 점은 &lt;b&gt;다음 순위를 건너뛰지 않는다는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;1837&quot; data-start=&quot;1639&quot; data-ke-size=&quot;size16&quot;&gt;예시) 두 명이 공동 2등이면 그다음은 4등이 아니라 3등으로 표시하기&lt;/p&gt;
&lt;pre id=&quot;code_1775835852003&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT BRANCH_ID, PRODUCT_NAME, SALES_AMOUNT,
DENSE_RANK() OVER (ORDER BY SALES_AMOUNT DESC) AS RANKING
FROM SALES_DATA;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;1&amp;rarr;2=2 &amp;rarr; 3&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;따라서 DENSE_RANK는 &lt;/span&gt;&lt;b&gt;촘촘한 순위&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;라고 보면 된다.&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;2085&quot; data-start=&quot;2064&quot; data-ke-size=&quot;size23&quot;&gt;ROW_NUMBER()&lt;/h3&gt;
&lt;p data-end=&quot;2262&quot; data-start=&quot;2087&quot; data-ke-size=&quot;size16&quot;&gt;ROW_NUMBER는 동점 여부와 상관없이 &lt;b&gt;모든 행에 고유한 번호를 순서대로 부여&lt;/b&gt;한다.&lt;br /&gt;따라서 같은 값이 있어도 순위를 공유하지 않고, 무조건 1, 2, 3, 4처럼 나간다.&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2262&quot; data-start=&quot;2087&quot; data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;pre id=&quot;code_1775835963657&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT BRANCH_ID, PRODUCT_NAME, SALES_AMOUNT,
ROW_NUMBER() OVER (ORDER BY SALES_AMOUNT DESC) AS RANKING
FROM SALES_DATA;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2497&quot; data-start=&quot;2416&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 1&amp;rarr;2&amp;rarr;3&amp;rarr;4&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;2497&quot; data-start=&quot;2416&quot; data-ke-size=&quot;size16&quot;&gt;ROW_NUMBER는 순위라기보다 &lt;b&gt;행 번호 매기기&lt;/b&gt;에 더 가깝다.&lt;/p&gt;
&lt;h3 data-end=&quot;2540&quot; data-start=&quot;2504&quot; data-ke-size=&quot;size23&quot;&gt;PARTITION BY를 함께 쓰는 ROW_NUMBER&lt;/h3&gt;
&lt;p data-end=&quot;2706&quot; data-start=&quot;2542&quot; data-ke-size=&quot;size16&quot;&gt;ROW_NUMBER에 PARTITION BY를 함께 쓰는 예시&lt;br /&gt;이 경우 전체 데이터를 한 번에 순위 매기는 것이 아니라, &lt;b&gt;지점별로 그룹을 나눠서 각 지점 안에서 다시 순위를 매기게 된다&lt;/b&gt;.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1775836096627&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT BRANCH_ID, PRODUCT_NAME, SALES_AMOUNT,
ROW_NUMBER() OVER (
PARTITION BY BRANCH_ID
ORDER BY SALES_AMOUNT DESC
) AS RN
FROM SALES_DATA;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3075&quot; data-start=&quot;2909&quot; data-ke-size=&quot;size16&quot;&gt;이 쿼리는 BRANCH_ID별로 그룹을 나누고, 각 지점 안에서 매출이 높은 순으로 1, 2, 3 순번을 붙인다.&lt;br /&gt;지점 001 안에서는 1~3, 지점 002 안에서도 다시 1~3처럼 번호가 새로 시작한다.&lt;/p&gt;
&lt;p data-end=&quot;3191&quot; data-start=&quot;3077&quot; data-ke-size=&quot;size16&quot;&gt;따라서 PARTITION BY는 GROUP BY처럼 행을 줄이는 것이 아니라, &lt;b&gt;계산 단위를 끊어주는 역할&lt;/b&gt;이라고 이해하면 된다.&lt;/p&gt;
&lt;h3 data-end=&quot;3214&quot; data-start=&quot;3198&quot; data-ke-size=&quot;size23&quot;&gt;4. 그룹 내 집계 함수&lt;/h3&gt;
&lt;p data-end=&quot;3429&quot; data-start=&quot;3216&quot; data-ke-size=&quot;size16&quot;&gt;윈도우 함수에서는 SUM, AVG 같은 집계 함수도 사용할 수 있다.&lt;br /&gt;&lt;b&gt;GROUP BY는 결과 행이 줄어들지만, 윈도우 함수는 행을 유지한 채로 각 행 옆에 누적합이나 평균 같은 값을 추가한다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-end=&quot;3451&quot; data-start=&quot;3431&quot; data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;pre id=&quot;code_1775836176485&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT BRANCH_ID, PRODUCT_NAME, SALES_AMOUNT, MONTH,
SUM(SALES_AMOUNT) OVER (
PARTITION BY BRANCH_ID
ORDER BY MONTH
) AS 누적합
FROM SALES_DATA;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3781&quot; data-start=&quot;3655&quot; data-ke-size=&quot;size16&quot;&gt;이 경우 각 지점별로 월 순서에 따라 누적합을 계산할 수 있다.&lt;br /&gt;따라서 지점 안에서 1월 값, 1월+2월 값, 1월+2월+3월 값이 순서대로 붙는 식이다.&lt;/p&gt;
&lt;h3 data-end=&quot;3805&quot; data-start=&quot;3788&quot; data-ke-size=&quot;size23&quot;&gt;WINDOWING 절&lt;/h3&gt;
&lt;p data-end=&quot;3970&quot; data-start=&quot;3807&quot; data-ke-size=&quot;size16&quot;&gt;윈도우 함수의 핵심 중 하나는 WINDOWING 절이다.&lt;br /&gt;이 절은 현재 행을 기준으로 &lt;b&gt;어디까지를 계산 범위로 볼지&lt;/b&gt; 정하는 역할을 한다.&lt;/p&gt;
&lt;p data-end=&quot;3970&quot; data-start=&quot;3807&quot; data-ke-size=&quot;size16&quot;&gt;WINDOWING 절을 생략하면 기본 범위가 적용된다&lt;/p&gt;
&lt;p data-end=&quot;3995&quot; data-start=&quot;3972&quot; data-ke-size=&quot;size16&quot;&gt;대표 표현은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4182&quot; data-start=&quot;3997&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4033&quot; data-start=&quot;3997&quot;&gt;UNBOUNDED PRECEDING : 그룹 내 첫 행부터&lt;/li&gt;
&lt;li data-end=&quot;4072&quot; data-start=&quot;4034&quot;&gt;UNBOUNDED FOLLOWING : 그룹 내 마지막 행까지&lt;/li&gt;
&lt;li data-end=&quot;4095&quot; data-start=&quot;4073&quot;&gt;CURRENT ROW : 현재 행&lt;/li&gt;
&lt;li data-end=&quot;4119&quot; data-start=&quot;4096&quot;&gt;RANGE : 값의 범위를 기준으로&lt;/li&gt;
&lt;li data-end=&quot;4182&quot; data-start=&quot;4120&quot;&gt;ROWS : 물리적인 행 기준으로&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4287&quot; data-start=&quot;4184&quot; data-ke-size=&quot;size16&quot;&gt;WINDOWING 절은 현재 행을 중심으로 어느 구간까지 합계를 낼 것인가를 정하는 규칙이라고 보면 된다&lt;/p&gt;
&lt;h3 data-end=&quot;4314&quot; data-start=&quot;4294&quot; data-ke-size=&quot;size23&quot;&gt;ROWS 기반 누적합 예시&lt;/h3&gt;
&lt;p data-end=&quot;4534&quot; data-start=&quot;4316&quot; data-ke-size=&quot;size16&quot;&gt;ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING 는 현재 행이 어디에 있든, 파티션의 첫 행부터 마지막 행까지를 모두 포함해서 합계를 계산한다. 그래서 같은 지점 안의 모든 행에 동일한 총합이 붙는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;pre id=&quot;code_1775836315617&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT BRANCH_ID, PRODUCT_NAME, SALES_AMOUNT, MONTH,
SUM(SALES_AMOUNT) OVER (
PARTITION BY BRANCH_ID
ORDER BY MONTH
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS 합계
FROM SALES_DATA;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4987&quot; data-start=&quot;4805&quot; data-ke-size=&quot;size16&quot;&gt;반면 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW로 바꾸면 누적합이 된다.&lt;br /&gt;즉 첫 행부터 현재 행까지만 더하기 때문에, 행이 내려갈수록 값이 커지는 형태가 된다&lt;/p&gt;
&lt;h3 data-end=&quot;5014&quot; data-start=&quot;4994&quot; data-ke-size=&quot;size23&quot;&gt;RANGE 기반 범위 합계&lt;/h3&gt;
&lt;p data-end=&quot;5273&quot; data-start=&quot;5016&quot; data-ke-size=&quot;size16&quot;&gt;RANGE BETWEEN 5000 PRECEDING AND 10000 FOLLOWING 일때 ROWS는 행의 개수를 기준으로 범위를 정하지만, RANGE는 정렬 기준 컬럼의 &lt;b&gt;값 차이&lt;/b&gt;를 기준으로 범위를 정한다.&lt;/p&gt;
&lt;p data-end=&quot;5273&quot; data-start=&quot;5016&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 SALES_AMOUNT를 기준으로 RANGE를 잡으면, 현재 행의 매출액 기준 &amp;plusmn;범위 안에 들어오는 값들을 함께 계산하게 된다.&lt;/p&gt;
&lt;p data-end=&quot;5414&quot; data-start=&quot;5275&quot; data-ke-size=&quot;size16&quot;&gt;따라서 RANGE는 물리적 위치가 아니라 &lt;b&gt;값의 범위로 묶는 방식&lt;/b&gt;이라는 점이 중요하다.&lt;/p&gt;
&lt;h3 data-end=&quot;5439&quot; data-start=&quot;5421&quot; data-ke-size=&quot;size23&quot;&gt;5. 그룹 내 행 순서 함수&lt;/h3&gt;
&lt;p data-end=&quot;5608&quot; data-start=&quot;5441&quot; data-ke-size=&quot;size16&quot;&gt;그룹 내 행 순서 함수로 LAG, LEAD가 있다&lt;br /&gt;이 함수들은 이전 행, 다음 행 값을 가져올 때 사용한다. 따라서 같은 그룹 안에서 &amp;ldquo;바로 전 값과 비교&amp;rdquo;하거나 &amp;ldquo;다음 값 확인&amp;rdquo; 같은 작업을 할 수 있다.&lt;/p&gt;
&lt;h3 data-end=&quot;5624&quot; data-start=&quot;5610&quot; data-ke-size=&quot;size23&quot;&gt;LAG()&lt;/h3&gt;
&lt;p data-end=&quot;5690&quot; data-start=&quot;5626&quot; data-ke-size=&quot;size16&quot;&gt;LAG는 현재 행을 기준으로 &lt;b&gt;이전 행의 값&lt;/b&gt;을 가져오는 함수다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;5802&quot; data-start=&quot;5692&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;5714&quot; data-start=&quot;5692&quot;&gt;컬럼 : 이전 행의 값을 가져올 대상&lt;/li&gt;
&lt;li data-end=&quot;5737&quot; data-start=&quot;5715&quot;&gt;오프셋 : 몇 번째 이전 행인지 지정&lt;/li&gt;
&lt;li data-end=&quot;5802&quot; data-start=&quot;5738&quot;&gt;디폴트 : 값이 없을 경우 반환할 기본값&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1775836539849&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT BRANCH_ID, PRODUCT_NAME, SALES_AMOUNT, MONTH,
LAG(SALES_AMOUNT, 1) OVER (
PARTITION BY BRANCH_ID
ORDER BY MONTH
) AS PREV_SALES
FROM SALES_DATA;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6131&quot; data-start=&quot;6016&quot; data-ke-size=&quot;size16&quot;&gt;이 쿼리는 같은 지점 안에서 바로 이전 월의 매출액을 현재 행 옆에 보여준다.&lt;br /&gt;첫 번째 행은 이전 값이 없으므로 NULL이 붙는다.&lt;/p&gt;
&lt;h3 data-end=&quot;6148&quot; data-start=&quot;6133&quot; data-ke-size=&quot;size23&quot;&gt;LEAD()&lt;/h3&gt;
&lt;p data-end=&quot;6289&quot; data-start=&quot;6150&quot; data-ke-size=&quot;size16&quot;&gt;LEAD는 현재 행을 기준으로 &lt;b&gt;다음 행의 값&lt;/b&gt;을 가져오는 함수다.&lt;br /&gt;LAG와 구조는 같지만 방향만 반대라고 보면 된다.&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;pre id=&quot;code_1775836569028&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT BRANCH_ID, PRODUCT_NAME, SALES_AMOUNT, MONTH,
LEAD(SALES_AMOUNT, 2, 999) OVER (
PARTITION BY BRANCH_ID
ORDER BY MONTH
) AS NEXT_SALES
FROM SALES_DATA;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6651&quot; data-start=&quot;6509&quot; data-ke-size=&quot;size16&quot;&gt;이 예시는 현재 행 기준 두 번째 이후 행 값을 가져오고, 값이 없으면 999를 반환한다.&lt;br /&gt;따라서 LEAD는 &amp;ldquo;다음 달 예측값 보기&amp;rdquo;, &amp;ldquo;다음 행과 비교하기&amp;rdquo; 같은 상황에서 유용하다.&lt;/p&gt;
&lt;h3 data-end=&quot;6675&quot; data-start=&quot;6658&quot; data-ke-size=&quot;size23&quot;&gt;6. 그룹 내 비율 함수&lt;/h3&gt;
&lt;p data-end=&quot;6848&quot; data-start=&quot;6677&quot; data-ke-size=&quot;size16&quot;&gt;그룹 내 비율 함수로 RATIO_TO_REPORT, PERCENT_RANK, CUME_DIST, NTILE가 있다&lt;br /&gt;이 함수들은 현재 행이 그룹 안에서 차지하는 비율이나 상대적 위치를 계산할 때 사용한다.&lt;/p&gt;
&lt;h3 data-end=&quot;6877&quot; data-start=&quot;6850&quot; data-ke-size=&quot;size23&quot;&gt;RATIO_TO_REPORT()&lt;/h3&gt;
&lt;p data-end=&quot;7021&quot; data-start=&quot;6879&quot; data-ke-size=&quot;size16&quot;&gt;RATIO_TO_REPORT는 &lt;b&gt;현재 행의 값이 그룹 전체 합계에서 차지하는 비율을 계산&lt;/b&gt;한다.&lt;br /&gt;예시)지점별 총매출 대비 각 상품 매출이 몇 %인지 구하기&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1775836675570&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT BRANCH_ID, PRODUCT_NAME, SALES_AMOUNT,
RATIO_TO_REPORT(SALES_AMOUNT) OVER (
PARTITION BY BRANCH_ID
) AS SALES_RATIO
FROM SALES_DATA;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7300&quot; data-start=&quot;7212&quot; data-ke-size=&quot;size16&quot;&gt;따라서 이 함수는 &amp;ldquo;이 상품이 지점 매출에서 얼마나 비중을 차지하는가&amp;rdquo;를 볼 때 유용하다.&lt;/p&gt;
&lt;h3 data-end=&quot;7326&quot; data-start=&quot;7302&quot; data-ke-size=&quot;size23&quot;&gt;PERCENT_RANK()&lt;/h3&gt;
&lt;p data-end=&quot;7502&quot; data-start=&quot;7328&quot; data-ke-size=&quot;size16&quot;&gt;PERCENT_RANK는 전체 데이터 안에서 현재 행이 어느 정도 백분위 위치에 있는지 계산한다.&lt;br /&gt;예시)각 지점 안에서 몇 번째 백분위에 해당하는지 구하시오&lt;/p&gt;
&lt;p data-end=&quot;7502&quot; data-start=&quot;7328&quot; data-ke-size=&quot;size16&quot;&gt;0부터 1 사이 값으로 상대적 순위를 나타낸다고 이해하면 된다.&lt;/p&gt;
&lt;h3 data-end=&quot;7525&quot; data-start=&quot;7504&quot; data-ke-size=&quot;size23&quot;&gt;CUME_DIST()&lt;/h3&gt;
&lt;p data-end=&quot;7666&quot; data-start=&quot;7527&quot; data-ke-size=&quot;size16&quot;&gt;CUME_DIST는 현재 행까지의 누적된 분포 비율을 계산한다.&lt;br /&gt;예시) 현재 값 이하의 행이 전체에서 몇 %인가요?&lt;/p&gt;
&lt;h3 data-end=&quot;7685&quot; data-start=&quot;7668&quot; data-ke-size=&quot;size23&quot;&gt;NTILE()&lt;/h3&gt;
&lt;p data-end=&quot;7881&quot; data-start=&quot;7687&quot; data-ke-size=&quot;size16&quot;&gt;NTILE은 데이터를 N개의 그룹으로 나눈다.&lt;/p&gt;
&lt;p data-end=&quot;7881&quot; data-start=&quot;7687&quot; data-ke-size=&quot;size16&quot;&gt;NTILE(4)를 사용하면 4분위처럼 데이터를 나눌 수 있다. 순위를 단순히 숫자로 주는 것이 아니라, &lt;b&gt;몇 번째 구간에 속하는지&lt;/b&gt;를 알려주는 함수다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;예시) 학생 점수를 4개의 분위로 나누시오&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 id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1775836906978&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT STUDENT_ID, SCORE,
NTILE(4) OVER (ORDER BY SCORE DESC) AS QUARTILE
FROM STUDENT_SCORE;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;8046&quot; data-start=&quot;8013&quot; data-ke-size=&quot;size23&quot;&gt;7. PARTITION BY와 GROUP BY의 차이&lt;/h3&gt;
&lt;p data-end=&quot;8046&quot; data-start=&quot;8013&quot; data-ke-size=&quot;size16&quot;&gt;이 둘은 이름이 비슷해서 많이 헷갈리지만 결과가 다르다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;8310&quot; data-start=&quot;8168&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;8222&quot; data-start=&quot;8168&quot;&gt;PARTITION BY : 원본 행을 유지한 상태에서 그룹별 계산 결과를 각 행에 붙인다.&lt;/li&gt;
&lt;li data-end=&quot;8310&quot; data-start=&quot;8223&quot;&gt;GROUP BY : 같은 그룹끼리 묶어서 행 수가 줄어든 집계 결과를 만든다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;8354&quot; data-start=&quot;8312&quot; data-ke-size=&quot;size16&quot;&gt;예시) 아래 쿼리는 원본 행을 유지하면서 지점별 합계를 각 행에 붙인다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;pre id=&quot;code_1775836982966&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT BRANCH_ID, PRODUCT_NAME, SALES_AMOUNT,
SUM(SALES_AMOUNT) OVER (PARTITION BY BRANCH_ID) AS BRANCH_TOTAL
FROM SALES_DATA;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8540&quot; data-start=&quot;8514&quot; data-ke-size=&quot;size16&quot;&gt;예시) 반면 아래 쿼리는 지점별로 하나의 행만 남긴다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;pre id=&quot;code_1775837000870&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT BRANCH_ID, SUM(SALES_AMOUNT) AS BRANCH_TOTAL
FROM SALES_DATA
GROUP BY BRANCH_ID;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8752&quot; data-start=&quot;8654&quot; data-ke-size=&quot;size16&quot;&gt;따라서 PARTITION BY는 붙이기, GROUP BY는 줄이기라고 생각하면 이해하기 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category> 꼬부기의 성장서재/이기적SQLD 기출문제</category>
      <author>서화</author>
      <guid isPermaLink="true">https://kkobug2.tistory.com/265</guid>
      <comments>https://kkobug2.tistory.com/265#entry265comment</comments>
      <pubDate>Fri, 10 Apr 2026 22:31:02 +0900</pubDate>
    </item>
    <item>
      <title>그룹 함수</title>
      <link>https://kkobug2.tistory.com/263</link>
      <description>&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;그룹 함수란?&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;여러 행을 하나의 집합으로 보고 요약 결과를 만들어내는 함수다&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;대표적으로 ROLLUP, CUBE, GROUPING SETS가 있다. 이 함수들을 사용하면 여러 번의 GROUP BY 결과를 한 번에 표현할 수 있다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;그룹 함수가 필요한 이유&lt;/h3&gt;
&lt;p data-end=&quot;816&quot; data-start=&quot;555&quot; data-ke-size=&quot;size16&quot;&gt;소계와 총계를 같이 보고 싶을 때 쿼리를 단순화할 수 있기 때문이다.&lt;br /&gt;예를 들어 지역별 매출 합계를 구하는 쿼리와 전체 매출 합계를 구하는 쿼리를 각각 작성할 수도 있다. 하지만 이런 방식은 집계 기준이 많아질수록 쿼리가 길어지고 번거로워진다.&lt;/p&gt;
&lt;p data-end=&quot;816&quot; data-start=&quot;555&quot; data-ke-size=&quot;size16&quot;&gt;그래서 ROLLUP, CUBE, GROUPING SETS를 사용하면 여러 단계의 집계 결과를 한 번에 만들 수 있다. 따라서 그룹 함수는 단순히 합계를 구하는 기능이 아닌 다양한 집계 수준을 한 번에 보여주기 위한 도구다&lt;/p&gt;
&lt;h3 data-end=&quot;925&quot; data-start=&quot;818&quot; data-ke-size=&quot;size23&quot;&gt;ROLLUP이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계층적인 다차원 집계를 쉽게 구할 수 있도록 도와주는 그룹 함수다.&lt;br /&gt;핵심은 &lt;b&gt;왼쪽에서 오른쪽 방향으로 소계가 누적된다&lt;/b&gt;는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; ROLLUP 작동 원리&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775665458307&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GROUP BY ROLLUP(컬럼1, 컬럼2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;1522&quot; data-start=&quot;1505&quot; data-ke-size=&quot;size16&quot;&gt;다음 순서의 집계가 만들어진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1606&quot; data-start=&quot;1524&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1544&quot; data-start=&quot;1524&quot;&gt;(컬럼1, 컬럼2) 기준 집계&lt;/li&gt;
&lt;li data-end=&quot;1560&quot; data-start=&quot;1545&quot;&gt;(컬럼1) 기준 집계&lt;/li&gt;
&lt;li data-end=&quot;1606&quot; data-start=&quot;1561&quot;&gt;전체 집계&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1730&quot; data-start=&quot;1608&quot; data-ke-size=&quot;size16&quot;&gt;뒤에서부터 하나씩 빠지면서 상위 수준 집계가 만들어진다&lt;br /&gt;그래서 ROLLUP은 소계와 총계를 같이 보고 싶을 때 특히 유용하다.&lt;/p&gt;
&lt;p data-end=&quot;1730&quot; data-start=&quot;1608&quot; data-ke-size=&quot;size16&quot;&gt;쉽게 생각해서 뒤에서 부터 하나씩 접는다고 생각하면 편하다&lt;/p&gt;
&lt;p data-end=&quot;1730&quot; data-start=&quot;1608&quot; data-ke-size=&quot;size16&quot;&gt;소계+총계 라는점을 까먹으면 안된다&lt;/p&gt;
&lt;p data-end=&quot;1730&quot; data-start=&quot;1608&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; ROLLUP 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775665594652&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT REGION, PRODUCT, SUM(SALES_AMOUNT)
FROM SALES
GROUP BY ROLLUP(REGION, PRODUCT);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;1942&quot; data-start=&quot;1919&quot; data-ke-size=&quot;size16&quot;&gt;이 쿼리는 다음 결과를 한 번에 보여준다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2012&quot; data-start=&quot;1944&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1957&quot; data-start=&quot;1944&quot;&gt;지역 + 상품별 매출&lt;/li&gt;
&lt;li data-end=&quot;1966&quot; data-start=&quot;1958&quot;&gt;지역별 소계&lt;/li&gt;
&lt;li data-end=&quot;2012&quot; data-start=&quot;1967&quot;&gt;전체 총계&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;East-Laptop, East-Tablet 같은 상세 집계가 먼저 나오고, 그다음 East 전체 소계, West 전체 소계가 나오며, 마지막에는 전체 합계가 나온다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; ROLLUP 특징&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ROLLUP은 &lt;b&gt;입력된 컬럼 순서가 중요하다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GROUP BY ROLLUP(A, B)와 GROUP BY ROLLUP(B, A)는 다른 결과를 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한점은 ROLLUP은 계층 순서에 따라 집계가 결정된다는 것이다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CUBE란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력한 컬럼들의 가능한 모든 조합에 대해 집계를 수행하는 그룹 함수다.&lt;br /&gt;&lt;b&gt;N개의 집합이면 2^N개의 집계가 생성된다 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ROLLUP보다 더 많은 경우의 수를 만든다고 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; CUBE 작동 원리&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775665907894&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GROUP BY CUBE(A, B)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;2683&quot; data-start=&quot;2668&quot; data-ke-size=&quot;size16&quot;&gt;다음 집계가 모두 생성된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2768&quot; data-start=&quot;2685&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2698&quot; data-start=&quot;2685&quot;&gt;(A, B) 집계&lt;/li&gt;
&lt;li data-end=&quot;2709&quot; data-start=&quot;2699&quot;&gt;(A) 집계&lt;/li&gt;
&lt;li data-end=&quot;2720&quot; data-start=&quot;2710&quot;&gt;(B) 집계&lt;/li&gt;
&lt;li data-end=&quot;2768&quot; data-start=&quot;2721&quot;&gt;전체 집계&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2919&quot; data-start=&quot;2770&quot; data-ke-size=&quot;size16&quot;&gt;ROLLUP과 다른 점은, ROLLUP은 계층적으로 줄어드는 집계만 만들지만 CUBE는 가능한 모든 부분집합 집계를 만든다는 점이다. 그래서 A 기준 집계도 나오고, B 기준 집계도 따로 나온다.&lt;/p&gt;
&lt;p data-end=&quot;2919&quot; data-start=&quot;2770&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; CUBE 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775665957092&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT REGION, PRODUCT, SUM(SALES_AMOUNT)
FROM SALES
GROUP BY CUBE(REGION, PRODUCT);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;3094&quot; data-start=&quot;3075&quot; data-ke-size=&quot;size16&quot;&gt;이 경우 결과에는 다음이 포함된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3175&quot; data-start=&quot;3096&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3109&quot; data-start=&quot;3096&quot;&gt;지역 + 상품별 집계&lt;/li&gt;
&lt;li data-end=&quot;3118&quot; data-start=&quot;3110&quot;&gt;지역별 소계&lt;/li&gt;
&lt;li data-end=&quot;3127&quot; data-start=&quot;3119&quot;&gt;상품별 소계&lt;/li&gt;
&lt;li data-end=&quot;3175&quot; data-start=&quot;3128&quot;&gt;전체 총계&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3325&quot; data-start=&quot;3177&quot; data-ke-size=&quot;size16&quot;&gt;CUBE는 ROLLUP보다 한 단계 더 넓은 범위의 집계를 제공한다.&lt;br /&gt;예를 들어 ROLLUP에서는 상품별 전체 합계가 따로 나오지 않을 수 있지만, CUBE에서는 상품만 기준으로 한 집계도 나온다.&lt;/p&gt;
&lt;p data-end=&quot;3325&quot; data-start=&quot;3177&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; CUBE 특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3402&quot; data-start=&quot;3372&quot;&gt;모든 집계 조합을 만들기 때문에 경우의 수가 많다.&lt;/li&gt;
&lt;li data-end=&quot;3447&quot; data-start=&quot;3403&quot;&gt;모든 차원의 집계를 수행하므로 연산량이 많아 CPU를 많이 사용할 수 있다.&lt;/li&gt;
&lt;li data-end=&quot;3517&quot; data-start=&quot;3448&quot;&gt;결과가 ROLLUP보다 더 많고 복잡할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GROUPING SETS란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GROUPING SETS는 원하는 집계 조합만 선택해서 생성하는 그룹 함수다.&lt;br /&gt;ROLLUP처럼 계층적으로 묶지도 않고, CUBE처럼 모든 조합을 다 만들지도 않는다. 대신 &lt;b&gt;필요한 GROUP BY 결과만 골라서 한 번에 합친다&lt;/b&gt;는 점이 핵심이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; GROUPING SETS 작동 원리&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775667608237&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GROUP BY GROUPING SETS(A, B)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3967&quot; data-start=&quot;3952&quot; data-ke-size=&quot;size16&quot;&gt;다음 두 집계만 만들어진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4030&quot; data-start=&quot;3969&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3979&quot; data-start=&quot;3969&quot;&gt;(A) 집계&lt;/li&gt;
&lt;li data-end=&quot;4030&quot; data-start=&quot;3980&quot;&gt;(B) 집계&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4134&quot; data-start=&quot;4032&quot; data-ke-size=&quot;size16&quot;&gt;A+B 상세 집계나 전체 집계는 포함되지 않는다.&lt;br /&gt;이 점이 ROLLUP, CUBE와 가장 다른 부분이다.&lt;/p&gt;
&lt;p data-end=&quot;4134&quot; data-start=&quot;4032&quot; data-ke-size=&quot;size16&quot;&gt;GROUPING SETS 예시&lt;/p&gt;
&lt;pre id=&quot;code_1775667644622&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT REGION, PRODUCT, SUM(SALES_AMOUNT)
FROM SALES
GROUP BY GROUPING SETS(REGION, PRODUCT);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;4323&quot; data-start=&quot;4301&quot; data-ke-size=&quot;size16&quot;&gt;이 경우 결과는 다음 두 종류만 나온다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4382&quot; data-start=&quot;4325&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4333&quot; data-start=&quot;4325&quot;&gt;지역별 집계&lt;/li&gt;
&lt;li data-end=&quot;4382&quot; data-start=&quot;4334&quot;&gt;상품별 집계&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4495&quot; data-start=&quot;4384&quot; data-ke-size=&quot;size16&quot;&gt;즉 지역+상품 상세 집계도 없고, 전체 합계도 없다.&lt;br /&gt;원하는 집계만 선택해서 보여주기 때문에 불필요한 결과를 줄일 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;4495&quot; data-start=&quot;4384&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; GROUPING SETS 특징&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;4495&quot; data-start=&quot;4384&quot; 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 data-end=&quot;4584&quot; data-start=&quot;4562&quot;&gt;원하는 집계 조합만 생성할 수 있다.&lt;/li&gt;
&lt;li data-end=&quot;4614&quot; data-start=&quot;4585&quot;&gt;불필요한 소계나 전체합계를 만들지 않을 수 있다.&lt;/li&gt;
&lt;li data-end=&quot;4686&quot; data-start=&quot;4615&quot;&gt;&amp;ldquo;정해진 집계만&amp;rdquo; 빠르게 처리하고 싶을 때 유용하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GROUPING 함수란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그룹 함수로 집계된 결과에서 NULL이 실제 데이터의 NULL인지, 아니면 집계 과정에서 만들어진 NULL인지 구분하기 위해 사용하는 함수다.&lt;br /&gt;ROLLUP이나 CUBE를 쓰면 소계, 총계 행에서 특정 컬럼 값이 NULL로 표시될 수 있는데, 이 NULL이 원래 데이터 때문인지 집계 과정 때문인지 헷갈릴 수 있다. 이때 GROUPING 함수를 사용하면 구분할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; GROUPING 사용 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775667806841&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT CASE
         WHEN GROUPING(REGION) = 1 AND GROUPING(PRODUCT) = 1 THEN '전체'
         ELSE REGION
       END AS REGION_INFO,
       CASE
         WHEN GROUPING(PRODUCT) = 1 THEN '소계'
         ELSE PRODUCT
       END AS PRODUCT_INFO,
       SUM(SALES_AMOUNT) AS TOTAL_SALES
FROM SALES
GROUP BY ROLLUP(REGION, PRODUCT);&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;이 쿼리는 ROLLUP 결과에서 NULL로 보이던 소계, 총계 행을 사람이 읽기 좋게 &amp;ldquo;소계&amp;rdquo;, &amp;ldquo;전체&amp;rdquo; 같은 문자로 바꿔서 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GROUPING 함수는 &lt;b&gt;집계 결과를 해석하기 쉽게 만들어주는 보조 도구&lt;/b&gt;라고 보면 된다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GROUP BY와 그룹 함수를 함께 해석하는 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그룹 함수를 단순히 외우는 것보다, &lt;b&gt;GROUP BY를 여러 번 수행한 결과를 한 번에 표현한 것처럼 해석하면 이해가 쉽다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; ROLLUP 해석&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775667909424&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GROUP BY ROLLUP(REGION, PRODUCT)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;5941&quot; data-start=&quot;5906&quot; data-ke-size=&quot;size16&quot;&gt;아래 세 GROUP BY 결과를 합친 것과 비슷하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;6047&quot; data-start=&quot;5943&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;5971&quot; data-start=&quot;5943&quot;&gt;GROUP BY REGION, PRODUCT&lt;/li&gt;
&lt;li data-end=&quot;5991&quot; data-start=&quot;5972&quot;&gt;GROUP BY REGION&lt;/li&gt;
&lt;li data-end=&quot;6047&quot; data-start=&quot;5992&quot;&gt;GROUP BY ()&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;6085&quot; data-start=&quot;6049&quot; data-ke-size=&quot;size16&quot;&gt;즉 상세 집계 &amp;rarr; 상위 집계 &amp;rarr; 전체 집계 순서로 생각하면 된다.&lt;/p&gt;
&lt;p data-end=&quot;6085&quot; data-start=&quot;6049&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; CUBE 해석&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775667939346&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GROUP BY CUBE(REGION, PRODUCT)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;6191&quot; data-start=&quot;6160&quot; data-ke-size=&quot;size16&quot;&gt;아래 네 가지 GROUP BY의 합처럼 볼 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;6318&quot; data-start=&quot;6193&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;6212&quot; data-start=&quot;6193&quot;&gt;GROUP BY REGION&lt;/li&gt;
&lt;li data-end=&quot;6233&quot; data-start=&quot;6213&quot;&gt;GROUP BY PRODUCT&lt;/li&gt;
&lt;li data-end=&quot;6262&quot; data-start=&quot;6234&quot;&gt;GROUP BY REGION, PRODUCT&lt;/li&gt;
&lt;li data-end=&quot;6318&quot; data-start=&quot;6263&quot;&gt;GROUP BY ()&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;6349&quot; data-start=&quot;6320&quot; data-ke-size=&quot;size16&quot;&gt;즉 가능한 모든 조합을 전부 만든다고 해석하면 된다.&lt;/p&gt;
&lt;p data-end=&quot;6349&quot; data-start=&quot;6320&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; GROUPING SETS 해석&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775667974263&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GROUP BY GROUPING SETS((PRODUCT), (), REGION)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;6597&quot; data-start=&quot;6448&quot; data-ke-size=&quot;size16&quot;&gt;원하는 GROUP BY 결과만 선택해서 합친다&lt;br /&gt;따라서 GROUPING SETS는 &amp;ldquo;여러 GROUP BY를 한 번에 실행하는 문법&amp;rdquo;처럼 생각하면 이해가 훨씬 쉬워진다.&lt;/p&gt;</description>
      <category> 꼬부기의 성장서재/이기적SQLD 기출문제</category>
      <author>서화</author>
      <guid isPermaLink="true">https://kkobug2.tistory.com/263</guid>
      <comments>https://kkobug2.tistory.com/263#entry263comment</comments>
      <pubDate>Wed, 8 Apr 2026 22:59:45 +0900</pubDate>
    </item>
    <item>
      <title>집합연산자</title>
      <link>https://kkobug2.tistory.com/262</link>
      <description>&lt;h2 data-end=&quot;89&quot; data-start=&quot;76&quot; data-ke-size=&quot;size26&quot;&gt;집합 연산자란?&lt;/h2&gt;
&lt;p data-end=&quot;379&quot; data-start=&quot;91&quot; data-ke-size=&quot;size16&quot;&gt;집합 연산자는 여러 SQL 쿼리 결과를 하나의 집합처럼 보고, 그 결과들을 결합하는 연산자다.&lt;br /&gt;합집합은 UNION, 중복을 허용한 합집합은 UNION ALL, 교집합은 INTERSECT, 차집합은 MINUS로 표현한다.&lt;/p&gt;
&lt;p data-end=&quot;379&quot; data-start=&quot;91&quot; data-ke-size=&quot;size16&quot;&gt;SQL Server에서는 MINUS 대신 EXCEPT를 사용한다. 따라서 집합 연산자는 각각의 SELECT 결과를 세로 방향으로 붙이고, 지정한 연산 방식에 따라 하나의 결과로 합치는 기능이라고 보면 된다.&lt;/p&gt;
&lt;p data-end=&quot;379&quot; data-start=&quot;91&quot; data-ke-size=&quot;size16&quot;&gt;조인과 집합연산자의 차이&lt;/p&gt;
&lt;p data-end=&quot;379&quot; data-start=&quot;91&quot; data-ke-size=&quot;size16&quot;&gt;조인은 두 테이블의 데이터를 가로 방향으로 붙이는 개념이고, 집합 연산자는 여러 결과를 세로 방향으로 합치는 개념이다&lt;/p&gt;
&lt;h3 data-end=&quot;379&quot; data-start=&quot;91&quot; data-ke-size=&quot;size23&quot;&gt;UNION과 UNION ALL&lt;/h3&gt;
&lt;p data-end=&quot;590&quot; data-start=&quot;576&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UNION&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;794&quot; data-start=&quot;592&quot; data-ke-size=&quot;size16&quot;&gt;UNION은 여러 결과를 합친 뒤 중복을 제거해서 반환하는 연산자다.&lt;/p&gt;
&lt;p data-end=&quot;794&quot; data-start=&quot;592&quot; data-ke-size=&quot;size16&quot;&gt;예시)한국 지사 직원과 미국 지사 직원 정보를 합쳐서 결과보기&lt;/p&gt;
&lt;pre id=&quot;code_1775571270172&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT EMP_ID, EMP_NAME
FROM EMPLOYEE_KOREA

UNION

SELECT EMP_ID, EMP_NAME
FROM EMPLOYEE_USA;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 결과를 UNION하면 내부적으로 정렬이 수행되고 동일한 값은 하나만 남긴다. 따라서 결과를 합칠 때 겹치는 행은 하나로 처리한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UNION ALL&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UNION ALL은 결과를 그대로 이어 붙이는 연산자다.&lt;br /&gt;UNION과 달리 중복 제거를 하지 않고, 정렬도 하지 않는다. 단순히 첫 번째 결과 밑에 두 번째 결과를 붙이는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;pre id=&quot;code_1775572847732&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT EMP_ID, EMP_NAME
FROM EMPLOYEE_KOREA

UNION ALL

SELECT EMP_ID, EMP_NAME
FROM EMPLOYEE_USA;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과가 두테이블에 모두 존재하면 UNION ALL 을 사용시 두번 출력된다 결과를 빠르게 단순 결합하고싶다면 UNION ALL이 더 적합하다 실무에서도 중복제거가 필요한게 아니라면 UNION ALL이 더 효율적일수 있다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;INTERSECT와 MINUS(EXCEPT)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; INTERSECT&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;INTERSECT는 두 결과의 교집합을 반환하는 연산자다 따라서 양쪽 결과 모두 존재하는 데이터만 출력된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;pre id=&quot;code_1775573258184&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT EMP_ID, EMP_NAME
FROM EMPLOYEE_KOREA

INTERSECT

SELECT EMP_ID, EMP_NAME
FROM EMPLOYEE_USA;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UNION ALL을 제외한 집합 연산자들은 모두 내부적으로 정렬과 중복 제거를 수행한다 그래서 INTERSECT도 단순 비교가 아니라 정리된 집합끼리의 교집합을 구하는 방식이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; MINUS(EXCEPT)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MINUS는 첫 번째 결과에서 두 번째 결과를 뺀 차집합을 반환하는 연산자다.&lt;br /&gt;따라서 A-B 개념이다 SQL Server에서는 같은 의미로 EXCEPT를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시) 한국 지사 직원 중에서 미국 지사에도 있는 직원 리스트&lt;/p&gt;
&lt;pre id=&quot;code_1775573331549&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT EMP_ID, EMP_NAME
FROM EMPLOYEE_KOREA

MINUS

SELECT EMP_ID, EMP_NAME
FROM EMPLOYEE_USA;&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;MINUS는 첫 번째 쿼리 기준으로 두 번째 쿼리와 겹치는 행을 제거한다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;집합 연산자의 특징&lt;/h3&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 data-end=&quot;2695&quot; data-start=&quot;2670&quot;&gt;UNION : 정렬 O, 중복 제거 O&lt;/li&gt;
&lt;li data-end=&quot;2725&quot; data-start=&quot;2696&quot;&gt;UNION ALL : 정렬 X, 중복 제거 X&lt;/li&gt;
&lt;li data-end=&quot;2755&quot; data-start=&quot;2726&quot;&gt;INTERSECT : 정렬 O, 중복 제거 O&lt;/li&gt;
&lt;li data-end=&quot;2829&quot; data-start=&quot;2756&quot;&gt;MINUS(EXCEPT) : 정렬 O, 중복 제거 O&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능적 측면에서 UNION ALL이 제일 가볍고 정확한 연산 필요시에는 UNION, INTERSECT, MINUS를 사용한다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;집합 연산자의 실행 순서&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;집합 연산자를 여러개 섞어서 사용할때 우선순위가 있는데 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;INTERSECT가 가장 먼저 실행되고, 그다음은 UNION, UNION ALL, MINUS(EXCEPT)가 같은 우선순위를 가진다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;예시)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775573937096&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT EMP_ID FROM EMPLOYEE_KOREA
UNION
SELECT EMP_ID FROM EMPLOYEE_USA
INTERSECT
SELECT EMP_ID FROM EMPLOYEE_KOREA;&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;괄호가 없기 때문에 B INTERSECT C가 먼저 실행되고, 그 결과를 A와 UNION하게 된다.&lt;br /&gt;집합 연산자는 위에서 아래로만 무조건 읽는 것이 아니라, INTERSECT 우선 실행을 먼저 고려해야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;괄호를 사용한 실행 순서 변경&lt;/h3&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;pre id=&quot;code_1775574019397&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(
  SELECT EMP_ID FROM EMPLOYEE_KOREA
  UNION
  SELECT EMP_ID FROM EMPLOYEE_USA
)
INTERSECT
SELECT EMP_ID FROM EMPLOYEE_KOREA;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 A와 B를 UNION한 결과를 만든 뒤, 그 결과를 C와 INTERSECT하게 된다.&lt;br /&gt;결국 집합 연산자 문제는 우선순위와 괄호 여부를 함께 봐야 한다는 점이 중요하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컬럼명은 첫 번째 쿼리를 따른다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;집합 연산자의 결과 컬럼명은 가장 처음 오는 SELECT문의 컬럼명을 따른다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;집합 연산자 사용 시 주의사항&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;집합 연산자에 참여하는 SELECT문은 컬럼 수가 반드시 같아야 한다&lt;/li&gt;
&lt;li&gt;자료형이 맞아야한다&lt;/li&gt;
&lt;li&gt;ORDER BY는 마지막에만 쓴다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;pre id=&quot;code_1775574222173&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT EMP_ID, EMP_NAME
FROM EMPLOYEE_KOREA
UNION
SELECT EMP_ID, EMP_NAME
FROM EMPLOYEE_USA
ORDER BY EMP_ID;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 UNION으로 합쳐진 전체 결과를 마지막에 EMP_ID 기준으로 정렬하게 된다.&lt;br /&gt;따라서 개별 SELECT 결과마다 ORDER BY를 거는 것이 아니라, 최종 결과 전체에 대해서만 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;</description>
      <category> 꼬부기의 성장서재/이기적SQLD 기출문제</category>
      <author>서화</author>
      <guid isPermaLink="true">https://kkobug2.tistory.com/262</guid>
      <comments>https://kkobug2.tistory.com/262#entry262comment</comments>
      <pubDate>Tue, 7 Apr 2026 23:13:46 +0900</pubDate>
    </item>
    <item>
      <title>Spring IoC 컨테이너란?</title>
      <link>https://kkobug2.tistory.com/261</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring IoC 컨테이너란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 생성,의존성 주입,생명주기 관리를 개발자 대신 수행하는 Spring의 핵심엔진입니다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; 전통적인 객체 생성 방식과 어떤 차이가 있는지?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;전통적인 방식에서는 개발자가 직접 new로 객체를 생성하고 의존성을 연결했습니다&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IoC 방식에서는 컨테이너가 @Component,@Service 같은 어노테이션을 스캔해서 Bean을 자동생성하고 필요한곳에 주입합니다 이것이 제어의 역전입니다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;제어의 역전 &amp;rarr; 무엇이 역전되는가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;loc(Inversion of Control,제어의 역전)는 프로그램의 제어흐름을 개발자에서 프레임워크로 넘기는 설계원칙이다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전통적 방식 &amp;rarr; 내 코드가 라이브러리를 호출&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;IoC 방식 &lt;/span&gt;&amp;rarr; 프레임워크가 내 코드를 호출&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제어가 역전 되는 3가지항목&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;전통적 방식&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;IoC 방식&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;객체생성&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;new 직접 호출&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;컨테이너 자동 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;의존성 연결&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;개발자가 직접 조립&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;자동주입(@Autowired)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;호출흐름&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&amp;nbsp;내 코드 &amp;rarr; 라이브러리&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;프레임워크 &amp;rarr; 내 코드&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;IoC VS DI 원칙과 구현 방법 차이&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;IoC &amp;rarr; 무엇을 할지에 대한 설계원칙&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;DI(의존성 주입) &amp;rarr; 어떻게 구현할지에 대한 구체척 방법&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;IoC 컨테이너의 3가지 핵심역할&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;객체 생성&lt;/p&gt;
&lt;pre id=&quot;code_1775466287914&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class UserService {
  // 개발자는 new UserService() 하지 않음
  // 컨테이너가 자동으로 인스턴스 생성
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성 주입&lt;/p&gt;
&lt;pre id=&quot;code_1775466344631&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class UserService {
  private final UserRepository userRepository;
  
  // 컨테이너가 UserRepository Bean을 찾아 자동 주입
  public UserService(UserRepository userRepository) {
    this.userRepository = userRepository;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생명주기 및 관리&lt;/p&gt;
&lt;pre id=&quot;code_1775466380198&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class CacheService {

  @PostConstruct
  void init() {
    // 컨테이너 시작 시 자동 호출
    // DB 커넥션 풀 초기화
  }

  @PreDestroy
  void cleanup() {
    // 컨테이너 종료 시 자동 호출
    // 자원 해제
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의존성 주입 3가지 방식 비교&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 51px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;생성자주입(권장)&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;세터주입(선택적)&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;필드주입(비권장)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;장점&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;1.final 사용가능 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;rarr; 불변성 보장&lt;br /&gt;필수 의존성 명시적 표현&lt;br /&gt;2. Spring 없이 new Service로 테스트&lt;br /&gt;3. 순환 의존성 시작시점에 즉시 감지&lt;br /&gt;4. NPE 원천 방지&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;1. 선택적 의존성&lt;br /&gt;2. 나중에 의존성 변경 가능함&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;코드가 짧고 간결해짐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;단점&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;의존성 많으면 생성자 복잡&lt;br /&gt;(lombok @ReQuiredArgsConstructor로 해결 가능 )&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;1. final 사용 불가&lt;br /&gt;2. 주입전 NPE&amp;nbsp; 위험&lt;br /&gt;3. 순환 의존성 숨김&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;1. new로 생성시 repo = null 이라서 테스트 불편함&lt;br /&gt;2. final 사용불가&lt;br /&gt;3. DI 컨테이너 없으면 동작 불가&lt;br /&gt;4. 의존성이 외부에서 보이지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코드 예시&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자 주입&lt;/p&gt;
&lt;pre id=&quot;code_1775467104372&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class UserService {
  private final UserRepository repo;
  
  // Spring 4.3+ 단일 생성자는 @Autowired 생략 가능
  public UserService(UserRepository repo) {
    this.repo = repo;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세터주입&lt;/p&gt;
&lt;pre id=&quot;code_1775467129828&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class UserService {
  private UserRepository repo; // final 불가
  
  @Autowired
  public void setRepo(UserRepository repo) {
    this.repo = repo;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필드주입&lt;/p&gt;
&lt;pre id=&quot;code_1775467156410&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class UserService {
  @Autowired
  private UserRepository repo; // final 불가
  
  // 생성자 없음 &amp;mdash; 간결함
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;BeanFactory VS ApplicationContext&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;BeanFactory &amp;rarr; 기본적인 의존성 주입(DI) 기능만 제공하는 최상위 인터페이스&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ApplicationContext &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;rarr; &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;BeanFactory 확장,엔터프라이즈 기능을 추가한 고급 컨테이너&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;실무에서는 ApplicationContext를 사용함 시작시점에 싱글톤 빈을 미리 생성해서 런타임오류를 조기 발견하고 국제화,이벤트 발생,AOP통합, 환경설정 등을 추가로 지원한다&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Bean 스코프 Singleton과 동시성 문제&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Bean은 기본적인 스코프가 싱글톤이다 컨테이너당 인스턴스가 1개만 생성되서 모든요청에서 공유된다&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;프로토타입 스코프는 getBean을 호출할때 마다 새 인스턴스를 만든다&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;싱글톤 빈은 무상태로 설계해야한다 인스턴스 변수에 요청별 데이터를 저장하면 여러 스레드가 같은 변수를 동시에 수정해서 Race Condition이 발생한다 따라서 상태 필요시 메서드 파라미터로 전달하거나 리퀘스트나 프로토타입스코프를 사용해야한다&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동시성 버그 예시&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: lab(48.4493 77.4328 61.5452); text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;❌ 잘못된 예 (상태 있는 Singleton)&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: lab(40.4273 67.2623 53.7441); text-align: start;&quot;&gt;&lt;code&gt;@Service
public class PriceService {
  private double discount; // 위험!
  
  // Thread A &amp;rarr; setDiscount(0.1)
  // Thread B &amp;rarr; setDiscount(0.2) 덮어씀
  // Thread A &amp;rarr; discount가 0.2 &amp;rarr; 버그!
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: lab(55.4814 75.0732 48.8528); text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;⚠ Race Condition &amp;rarr; 데이터 오염&lt;/p&gt;
&lt;p style=&quot;color: lab(59.0978 -58.6621 41.2579); text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;✅ 올바른 예 (무상태 설계)&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: lab(47.0329 -47.0239 31.4788); text-align: start;&quot;&gt;&lt;code&gt;@Service
public class PriceService {
  // 상태 없음 &amp;mdash; 모든 값을 파라미터로 전달
  public double calculate(
    double price,
    double discount // 파라미터로 전달
  ) {
    return price * (1 - discount);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: lab(59.0978 -58.6621 41.2579); text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;✓ 스레드 안전 &amp;mdash; 상태 없음&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 89px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 21px;&quot;&gt;스코프&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 21px;&quot;&gt;인스턴스수&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 21px;&quot;&gt;생명주기&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 21px;&quot;&gt;주 사용처&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; Singleton &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; 컨테이너당 1개 &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; 전체 생명주기 &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; 무상태 서비스 &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; Prototype &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; 요청마다 새 인스턴스 &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; 컨테이너 관리 안 함 &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; 상태 있는 객체 &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; &lt;span style=&quot;text-align: start;&quot;&gt;Request&lt;/span&gt; &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; HTTP 요청당 1개 &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; 요청 시작~종료 &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; 요청별 로깅 &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; &lt;span style=&quot;text-align: start;&quot;&gt;Session&lt;/span&gt; &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; HTTP 세션당 1개 &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; 세션 시작~만료 &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; 로그인 사용자 정보 &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컨테이너 초기화 과정 - Bean 생성하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BeanDefinition 로드 &amp;rarr; BeanFactoryPostProcessor 실행 &amp;rarr; Bean 인스턴스화 &amp;rarr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성 주입 &amp;rarr; BeanPostProcessor 실행 &amp;rarr; 컨테이너 준비 완료&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자주발생하는 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;두개의 Bean이 서로 의존하면 BeanCurrentlyInCreationException&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;발생함&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775485057105&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service public class ServiceA { public ServiceA(ServiceB b) { ... } }
@Service public class ServiceB { public ServiceB(ServiceA a) { ... } }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt; 원인:&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;ServiceA 생성 시 ServiceB 필요 &amp;rarr; ServiceB 생성 시 ServiceA 필요 &amp;rarr; 무한 루프. Spring Boot 2.6+에서는 시작 자체를 실패시킴&lt;/span&gt;&lt;/span&gt;&lt;/p&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;설계리팩토링(권장) &amp;rarr; ServiceA와 ServiceB가 공유하는 로직을 ServiceC로 분리해 단방향 의존으로 전환&lt;/li&gt;
&lt;li&gt;@Lazy 사용 &amp;rarr; 한쪽 생성자 파라미터에 @Lazy 붙여 프록시 주입으로 순환 회피&lt;/li&gt;
&lt;li&gt;세터 주입(비권장) &amp;rarr; 객체 생성 후 주입하여 우회&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; &lt;span style=&quot;text-align: start;&quot;&gt;NoSuchBeanDefinitionException &amp;mdash; 필요한 Bean을 찾을 수 없음&lt;/span&gt; &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;원인과 해결&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어노테이션 누락 &amp;rarr; 클래스에&amp;nbsp;@Repository&amp;nbsp;또는&amp;nbsp;@Component&amp;nbsp;추가&lt;/li&gt;
&lt;li&gt;컴포넌트 스캔 범위 밖 &amp;rarr; @ComponentScan(basePackages = ...)&amp;nbsp;범위 지정&lt;/li&gt;
&lt;li&gt;동일 타입 Bean 여러개 &amp;rarr; @Primary&amp;nbsp;또는&amp;nbsp;@Qualifier로 명시적 지정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; Singleton Bean의 동시성 문제 &amp;mdash; 인스턴스 변수에 요청별 데이터 저장 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775485598005&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class PriceCalculator {
    private double discount;  // 위험: 모든 스레드가 공유
    public void setDiscount(double d) { this.discount = d; }
    public double calculate(double price) { return price * (1 - discount); }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt; 원인:&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;Thread A가 discount=0.1 설정 &amp;rarr; Thread B가 discount=0.2로 덮어씀 &amp;rarr; Thread A의 계산이 잘못된 할인율 적용함&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;해결&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;무상태로 설계 &amp;rarr; &lt;span style=&quot;color: #333333;&quot;&gt;calculate(double price, double discount)&lt;span style=&quot;text-align: start;&quot;&gt; 상태를 파라미터로 전달&lt;/span&gt; &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;ThreadLocal 사용 &amp;rarr; &lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;스레드별로 데이터를 격리 (단, 요청 종료 후&amp;nbsp;&lt;/span&gt;remove()&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;필수)&lt;/span&gt; &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Prototype/Request 스코프 &amp;rarr; &lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;span style=&quot;text-align: start;&quot;&gt;요청마다 인스턴스 분리&lt;/span&gt; &lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  꼬부기 LV.1 | 개념&amp;bull;기초/ 물대포(핵심개념)</category>
      <author>서화</author>
      <guid isPermaLink="true">https://kkobug2.tistory.com/261</guid>
      <comments>https://kkobug2.tistory.com/261#entry261comment</comments>
      <pubDate>Mon, 6 Apr 2026 17:36:27 +0900</pubDate>
    </item>
    <item>
      <title>SQL 표준 조인 정리</title>
      <link>https://kkobug2.tistory.com/257</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;INNER JOIN이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;INNER JOIN은 두 테이블 사이의 조인 조건에 일치하는 행만 반환하는 조인 방식이다.&lt;br /&gt;두 테이블에 공통으로 존재하는 데이터만 결과에 포함되고, 조건에 맞지 않는 데이터는 제외된다. 쉽게 말하면 교집합만 가져오는 조인이라고 생각하면된다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;332&quot; data-origin-height=&quot;265&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BM9F5/dJMcajn7TuP/vMICgllzaODKBuLuDX4Lv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BM9F5/dJMcajn7TuP/vMICgllzaODKBuLuDX4Lv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BM9F5/dJMcajn7TuP/vMICgllzaODKBuLuDX4Lv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBM9F5%2FdJMcajn7TuP%2FvMICgllzaODKBuLuDX4Lv0%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;332&quot; height=&quot;265&quot; data-origin-width=&quot;332&quot; data-origin-height=&quot;265&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시) 회원 테이블에 A0001,A0002,A0003이 있고 회원 연락처 테이블에 A0001, A0002, A0004가 있을때 두테이블에 모두 존재하는 정보 구하기&lt;/p&gt;
&lt;pre id=&quot;code_1775145123771&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT A.회원ID
     , A.이름
     , B.연락처
FROM 회원 A
   , 회원연락처 B
WHERE A.회원ID = B.회원ID;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원ID 기준으로 INNER JOIN하면 두 테이블에 모두 존재하는 A0001, A0002만 결과에 포함된다. A0003은 연락처 정보가 없어서 빠지고, A0004는 회원 정보가 없어서 빠진다.&lt;/p&gt;
&lt;p data-end=&quot;736&quot; data-start=&quot;710&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;INNER JOIN의 실행 흐름&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;986&quot; data-start=&quot;738&quot; data-ke-size=&quot;size16&quot;&gt;INNER JOIN도 기본 흐름은 같다.&lt;br /&gt;먼저 FROM절에서 두 테이블이 함께 들어오면 가능한 모든 조합이 만들어지고, 그다음 WHERE절에서 조인 조건에 맞는 행만 남긴다. 마지막으로 SELECT절에서 원하는 컬럼만 출력한다. 결국 INNER JOIN은 &amp;ldquo;처음부터 딱 맞는 값만 붙는 것&amp;rdquo;이 아니라, 가능한 조합을 만든 뒤 조인 조건으로 걸러낸다&lt;/p&gt;
&lt;h3 data-end=&quot;986&quot; data-start=&quot;738&quot; data-ke-size=&quot;size23&quot;&gt;OUTER JOIN이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OUTER JOIN은 조인 조건에 일치하지 않는 데이터도 결과에 포함시키는 조인 방식이다.&lt;br /&gt;특정 테이블의 모든 데이터를 기준으로 유지하고, 반대편 테이블에서 일치하는 값이 없으면 NULL을 채워 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;INNER JOIN이 교집합만 반환한다면, OUTER JOIN은 한쪽에만 있는 값도 버리지 않고 보여주는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; OUTER JOIN이 필요한 이유&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;INNER JOIN으로 회원과 회원연락처를 조인하면 연락처가 없는 A0003 회원은 결과에서 사라진다.&lt;br /&gt;하지만 연락처가 없더라도 회원 자체는 조회해야 할 때가 있다. 이럴 때 OUTER JOIN을 사용하면 회원 테이블의 모든 행을 유지하면서, 연락처가 없는 경우에는 연락처 컬럼만 NULL로 채워서 보여줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; Oracle 방식의 OUTER JOIN&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Oracle 전용 문법인 (+) 기호를 사용한다&lt;/p&gt;
&lt;pre id=&quot;code_1775147796795&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT A.회원ID
     , A.이름
     , B.연락처
FROM 회원 A
   , 회원연락처 B
WHERE A.회원ID = B.회원ID(+);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 (+)는 반대편 테이블 쪽에 붙는다.&lt;br /&gt;따라서 B.회원ID(+)라고 적었기 때문에 B 테이블에 일치하는 값이 없어도 A 테이블의 행은 유지하겠다는 뜻이다. Oracle 전용이고 SQL Server에서는 사용할 수 없다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ANSI 표준 조인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ANSI는 여러 DBMS 간 SQL 문법을 통일하기 위해 표준 SQL 문법을 제정한 기관이다.&lt;br /&gt;Oracle 전용 문법인 (+)는 MySQL, SQL Server 등에서는 지원되지 않기 때문에, 보다 보편적으로 ANSI 표준 문법을 사용하는 것이 좋다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;INNER JOIN, LEFT OUTER JOIN, ON, USING 같은 문법이 표준 조인 문법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; ANSI 방식의 INNER JOIN 구조&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;2343&quot; data-start=&quot;2280&quot; data-ke-size=&quot;size16&quot;&gt;ANSI 방식으로 INNER JOIN을 쓰면 조인 조건은 ON 뒤에 적고, 일반 조건은 WHERE절에 적는다.&lt;/p&gt;
&lt;pre id=&quot;code_1775147956373&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT A.회원ID, B.연락처
FROM 회원 A
INNER JOIN 회원연락처 B
   ON (A.회원ID = B.회원ID)
WHERE A.회원ID = 'A0001'
  AND B.구분코드 = '휴대폰';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조가 기존의 Oracle 스타일 조인과 같은 의미이고 INNER JOIN 자체는 두 테이블을 어떻게 연결할지 나타내고, ON은 조인 조건, WHERE는 일반 조건을 적는 자리라고 정리하면 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ON과 USING 문법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ON 문법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ON은 조인 조건을 직접 명시하는 문법이다.&lt;br /&gt;두 테이블을 어떤 컬럼으로 연결할지 정확하게 적을 수 있어서 가장 많이 쓰이는 방식이다. 특히 컬럼명이 다르거나, 조인 조건을 명확하게 보여주고 싶을 때 ON이 더 직관적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;USING 문법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;USING은 두 테이블이 같은 이름의 공통 컬럼을 가지고 있을 때 사용할 수 있는 문법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시) 두 테이블 모두 회원ID라는 컬럼을 가지고 있다면, 그 컬럼명을 USING 안에 적는 것만으로 동등 조인을 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1775148094167&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT 회원ID, B.연락처
FROM 회원 A
INNER JOIN 회원연락처 B
USING (회원ID);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;USING은 공통 컬럼을 기준으로 자동 동등 조인을 만든다. 그리고 USING에 적은 공통 컬럼은 하나로 병합되어 보며, 별칭을 붙일 수 없다. 또 SQL Server에서는 지원되지 않고, 제약이 많아서 실무에서는 USING보다 ON을 더 많이 쓴다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;NATURAL JOIN이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NATURAL JOIN은 조인 조건을 직접 쓰지 않아도, 두 테이블 간 동일한 컬럼명을 자동으로 찾아 조인 조건으로 설정하는 방식이다.&lt;br /&gt;따라서 ON이나 USING 없이도 공통 컬럼명이 있으면 자동으로 INNER JOIN처럼 동작한다.&lt;/p&gt;
&lt;pre id=&quot;code_1775148184609&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT *
FROM 회원 NATURAL JOIN 회원연락처;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; NATURAL JOIN의 특징&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NATURAL JOIN이 ON, USING보다 더 간단해 보이지만 실무에서는 거의 사용되지 않는다&lt;br /&gt;이유는 모든 공통 컬럼이 자동 조인 대상이 되기 때문에 예기치 못한 결과가 나올 수 있기 때문이다. 또 별칭 사용에 제약이 있고, 컬럼명이 다르면 조인이 되지 않으며, SQL Server는 NATURAL JOIN을 지원하지 않는다. 그래서 문법적으로는 간단하지만, 실무에서는 명시적인 ON을 사용하는 것이 더 안전하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;LEFT OUTER JOIN이란?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;156&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNxgJw/dJMcabX0OJ6/oT9lTrRfRsclW74Wm1mPh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNxgJw/dJMcabX0OJ6/oT9lTrRfRsclW74Wm1mPh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNxgJw/dJMcabX0OJ6/oT9lTrRfRsclW74Wm1mPh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNxgJw%2FdJMcabX0OJ6%2FoT9lTrRfRsclW74Wm1mPh0%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;156&quot; height=&quot;174&quot; data-origin-width=&quot;156&quot; data-origin-height=&quot;174&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LEFT OUTER JOIN은 왼쪽 테이블의 모든 행을 기준으로 결과를 반환하는 조인이다.&lt;br /&gt;오른쪽 테이블에 일치하는 데이터가 있으면 붙이고, 없으면 오른쪽 컬럼을 NULL로 채운다.&lt;/p&gt;
&lt;pre id=&quot;code_1775148505810&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT A.회원ID, B.연락처
FROM 회원 A
LEFT OUTER JOIN 회원연락처 B
  ON A.회원ID = B.회원ID;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원 테이블 A를 기준으로 모든 회원 데이터를 출력하고, 연락처 테이블 B에 매칭되지 않는 A0003은 연락처를 NULL로 보여준다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RIGHT OUTER JOIN이란?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;167&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNUl7T/dJMcah40aW7/pKWYwt0Q1PD1n46WZSA9tK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNUl7T/dJMcah40aW7/pKWYwt0Q1PD1n46WZSA9tK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNUl7T/dJMcah40aW7/pKWYwt0Q1PD1n46WZSA9tK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNUl7T%2FdJMcah40aW7%2FpKWYwt0Q1PD1n46WZSA9tK%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;167&quot; height=&quot;162&quot; data-origin-width=&quot;167&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RIGHT OUTER JOIN은 오른쪽 테이블의 모든 행을 기준으로 결과를 반환하는 조인이다.&lt;br /&gt;왼쪽 테이블과 일치하는 값이 있으면 붙이고, 없으면 왼쪽 컬럼을 NULL로 채운다. LEFT OUTER JOIN의 기준 방향만 반대로 생각하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1775148659145&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT A.회원ID AS 회원_회원ID
     , B.회원ID AS 회원연락처_회원ID
     , B.연락처 AS 연락처
FROM 회원 A
RIGHT OUTER JOIN 회원연락처 B
  ON A.회원ID = B.회원ID;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원연락처 테이블 B를 기준으로 모든 데이터를 유지하기 때문에, 회원 정보에는 없는 A0004 연락처도 결과에 포함되고, 그때 회원 테이블 쪽 컬럼은 NULL로 표시된다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;FULL OUTER JOIN이란?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;173&quot; data-origin-height=&quot;157&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/P4yDD/dJMcagE2xWP/KY9tlbUKO9ziqnONHXVvw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/P4yDD/dJMcagE2xWP/KY9tlbUKO9ziqnONHXVvw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/P4yDD/dJMcagE2xWP/KY9tlbUKO9ziqnONHXVvw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FP4yDD%2FdJMcagE2xWP%2FKY9tlbUKO9ziqnONHXVvw0%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;173&quot; height=&quot;157&quot; data-origin-width=&quot;173&quot; data-origin-height=&quot;157&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FULL OUTER JOIN은 LEFT OUTER JOIN과 RIGHT OUTER JOIN을 합친 형태다.&lt;br /&gt;즉 양쪽 테이블 모두를 기준으로 해서 조인을 수행하고, 공통된 값은 매칭하고, 어느 한쪽에만 있는 데이터도 모두 결과에 포함한다. 매칭되지 않는 쪽 컬럼에는 NULL이 들어간다.&lt;/p&gt;
&lt;pre id=&quot;code_1775148759817&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT A.회원ID AS 회원_회원ID
     , B.회원ID AS 회원연락처_회원ID
     , B.연락처 AS 연락처
FROM 회원 A
FULL OUTER JOIN 회원연락처 B
  ON A.회원ID = B.회원ID;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FULL OUTER JOIN은 사실상 LEFT OUTER JOIN 결과와 RIGHT OUTER JOIN 결과를 UNION 한 것과 같은 의미라고 설명한다. 그래서 양쪽에만 존재하는 값도 전부 포함된다는 점이 핵심이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;INNER JOIN과 OUTER JOIN 차이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;INNER JOIN은 조인 조건에 일치하는 행만 반환한다.&lt;br /&gt;반면 OUTER JOIN은 조인에 실패한 데이터도 한쪽 기준 테이블에 속해 있다면 결과에 포함시킨다. 따라서 INNER JOIN은 교집합 중심이고, OUTER JOIN은 기준 테이블의 데이터 보존이 목적이라고 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 회원 목록 전체를 보고 싶다면 LEFT OUTER JOIN이 맞고, 둘 다 존재하는 데이터만 보고 싶다면 INNER JOIN이 맞다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ON, USING, NATURAL JOIN 비교&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ON은 가장 명시적이고 제약이 적어서 실무에서 가장 많이 사용한다.&lt;br /&gt;USING은 공통 컬럼명이 같을 때 간단하게 쓸 수 있지만 제약이 있다. NATURAL JOIN은 더 짧게 쓸 수 있지만 자동 조인이라는 점 때문에 예측하기 어려운 결과가 나올 수 있어서 실무에서는 거의 쓰지 않는다. 결국 &amp;ldquo;시험용으로 개념은 알아두되, 실무에서는 ON 중심으로 기억하면 된다&amp;rdquo; 의 흐름이다&lt;/p&gt;</description>
      <category> 꼬부기의 성장서재/이기적SQLD 기출문제</category>
      <author>서화</author>
      <guid isPermaLink="true">https://kkobug2.tistory.com/257</guid>
      <comments>https://kkobug2.tistory.com/257#entry257comment</comments>
      <pubDate>Thu, 2 Apr 2026 22:48:51 +0900</pubDate>
    </item>
    <item>
      <title>JOIN</title>
      <link>https://kkobug2.tistory.com/256</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;조인이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조인(JOIN)은 두 개 이상의 테이블에 흩어져 있는 데이터를 한 번에 조회할 수 있게 해주는 기능이다. SQL에서는 꼭 이해하고 넘어가야 하는 핵심 개념 중 하나다. 실무에서는 필요한 정보가 여러 테이블에 나뉘어 저장되어 있는 경우가 많기 때문에, 결국 원하는 결과를 얻으려면 조인을 사용할 수밖에 없다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;조인을 배우기 전에 알아야 할 것&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통은 테이블 하나만 지정해서 조회한다&lt;/p&gt;
&lt;pre id=&quot;code_1775056227885&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT *
FROM STUDENT;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FROM절에 테이블을 여러 개 나열하기&lt;/p&gt;
&lt;pre id=&quot;code_1775056338382&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT *
FROM STUDENT, MAJOR;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이경우 SQL은 두 테이블의 가능한 모든 조합을 먼저 만든다 따라서 한쪽 테이블의 각 행과 다른 테이블의 각 행을&amp;nbsp; 전부 붙여보는 방식으로 결과를 만든다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FROM절에서 테이블을 여러개 사용하려면 서로 어떤 기준으로 연결할지 만드시 정해줘야한다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테이블에 별칭을 주는 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블이 2개 이상이면 같은 이름의 컬럼이 존재할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때 어떤 테이블의 컬럼인지 구분하지 않으면 오류가 발생한다 이 문제를 해결하기 위해 테이블에 별칭을 줄수있다&lt;/p&gt;
&lt;pre id=&quot;code_1775056973955&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT A.COL1
     , A.COL2
     , B.COL2
     , B.COL3
FROM TAB1 A, TAB2 B;&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;&lt;b&gt; 별칭 사용 시 주의사항&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블에 별칭을 부여했다면 그 뒤에는 원래 테이블명 대신 별칭을 사용해야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;조인의 개념과 필요성&lt;/h3&gt;
&lt;p data-end=&quot;2205&quot; data-start=&quot;1933&quot; data-ke-size=&quot;size16&quot;&gt;조인은 여러 테이블에 흩어져 있는 데이터를 한 번에 조회하기 위해 필요한 기술이다.&lt;br /&gt;예시) 원하는 데이터를 얻으려면 원래 5개의 테이블을 각각 따로 조회해야 할 수도 있다. 하지만 조인을 사용하면 한 번의 SQL로 필요한 데이터를 가져올 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2412&quot; data-start=&quot;2207&quot; data-ke-size=&quot;size16&quot;&gt;또 데이터베이스는 정규화를 통해 하나의 테이블을 여러 개로 나누는 구조를 자주 사용한다.&lt;br /&gt;이렇게 나누면 중복과 이상 현상을 줄일 수 있지만, 대신 필요한 정보를 다시 합쳐서 봐야 할 때 조인이 필요해진다. 따라서 데이터 품질을 위해 테이블을 나눈 대신, 조회 단계에서 조인으로 다시 연결하는 것이다.&lt;/p&gt;
&lt;h3 data-end=&quot;2412&quot; data-start=&quot;2207&quot; data-ke-size=&quot;size23&quot;&gt;조인의 실행 원리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조인의 실행 원리를 이해하려면 SQL이 먼저 가능한 모든 조합을 만들고, 그다음 필요한 행만 필터링한다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;카티션 곱(Cartesian Product)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FROM절에 두 테이블을 넣으면 기본적으로 가능한 모든 조합이 만들어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시) 회원테이블에 3행이 있고 회원연락처테이블에 4행이 있을때 총 12개의 조합이 만들어진다&lt;/p&gt;
&lt;pre id=&quot;code_1775057767678&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT *
FROM 회원, 회원연락처;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 결과는 대부분의 경우 너무 많고, 서로 관련 없는 데이터까지 섞여 있다. 그래서 조인에서는 여기서 끝나면 안 되고, 실제로 연결되어야 하는 행끼리만 남기기 위한 조건이 필요하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;조인 조건&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조인 조건은 WHERE절에서 두 테이블의 연관 관계를 지정해서, 서로 관련 있는 데이터만 남기도록 하는 조건이다.&lt;/p&gt;
&lt;pre id=&quot;code_1775057847456&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT *
FROM 회원 A, 회원연락처 B
WHERE A.회원ID = B.회원ID;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;3597&quot; data-start=&quot;3402&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 카티션 곱으로 만들어진 모든 조합 중에서 회원ID가 일치하는 행만 필터링된다. 결과적으로 회원 정보와 그 회원의 연락처가 연결된 형태로 조회된다. 따라서 조인은 여러 테이블을 나열한 뒤, WHERE절에서 관계를 지정해 필요한 데이터만 남기는 방식이다&lt;/p&gt;
&lt;p data-end=&quot;3712&quot; data-start=&quot;3599&quot; data-ke-size=&quot;size16&quot;&gt;일반적으로 조인 조건을 정할 때 두 테이블의 관계를 나타내는 기본 키(PK)와 외래 키(FK)를 많이 이용한다&lt;/p&gt;
&lt;h3 data-end=&quot;3712&quot; data-start=&quot;3599&quot; data-ke-size=&quot;size23&quot;&gt;일반 조건 추가하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조인 조건만으로 끝나는 것이 아니라, 여기에 원하는 일반 조건을 더 붙일 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시) 회원아이디가 A0001이고 연락처 구분코드가 휴대폰인것만 조회하고 싶을때&lt;/p&gt;
&lt;pre id=&quot;code_1775058598603&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT *
FROM 회원 A, 회원연락처 B
WHERE A.회원ID = B.회원ID
  AND A.회원ID = 'A0001'
  AND B.구분코드 = '휴대폰';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 두 테이블의 관계도 맞고, 동시에 내가 원하는 회원과 원하는 연락처 종류만 남게 된다. 따라서 WHERE절 안에는 조인 조건과 일반 필터 조건이 함께 들어갈 수 있다.&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;h3 data-ke-size=&quot;size23&quot;&gt;동등 조인(Equi-Join)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조인 조건에 등호 비교연산자 = 만 사용하는 조인이다 가장 일반적이고 많이 사용된다 PK-FK 관계 조인의 대부분이 여기에 해당된다&lt;/p&gt;
&lt;pre id=&quot;code_1775058782286&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT *
FROM 고객 A, 주문 B
WHERE A.고객ID = B.고객ID;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고객 테이블과 주문 테이블에서 고객ID가 같은 행끼리 연결하는 방식이다.&lt;br /&gt;실무에서 우리가 흔히 말하는 조인의 대부분은 사실상 이 동등 조인이라고 보면 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비동등 조인(Non-Equi Join)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동등 조인은 조인 조건에 = 이외의 비교 연산자, 예를 들어 &amp;gt;, &amp;lt;, BETWEEN, != 등을 사용하는 조인이다. 범위 매칭이 필요할 때 비동등 조인을 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1775058863707&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT *
FROM 급여등급 A, 사원 B
WHERE B.급여 BETWEEN A.최소급여 AND A.최대급여;&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;b&gt; 동등 조인과 비동등 조인의 구분&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조인 조건에 =만 쓰면 동등 조인이고, = 외의 비교 연산자가 들어가면 비동등 조인이다.&lt;/p&gt;</description>
      <category> 꼬부기의 성장서재/이기적SQLD 기출문제</category>
      <author>서화</author>
      <guid isPermaLink="true">https://kkobug2.tistory.com/256</guid>
      <comments>https://kkobug2.tistory.com/256#entry256comment</comments>
      <pubDate>Wed, 1 Apr 2026 23:21:50 +0900</pubDate>
    </item>
    <item>
      <title>ORDER BY절</title>
      <link>https://kkobug2.tistory.com/255</link>
      <description>&lt;h3 data-end=&quot;90&quot; data-start=&quot;75&quot; data-ke-size=&quot;size23&quot;&gt;✔️ ORDER BY란?&lt;/h3&gt;
&lt;p data-end=&quot;307&quot; data-start=&quot;92&quot; data-ke-size=&quot;size16&quot;&gt;ORDER BY는 조회된 결과를 특정 컬럼 기준으로 정렬할 때 사용하는 문법이다 SQL 실행 순서상 가장 마지막에 실행되며, SELECT로 뽑아낸 결과를 다시 원하는 기준으로 재배치하는 역할을 한다&lt;/p&gt;
&lt;h3 data-end=&quot;307&quot; data-start=&quot;92&quot; data-ke-size=&quot;size23&quot;&gt;✔️ ORDER BY 실행 순서&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ORDER BY는 FROM, WHERE, GROUP BY, HAVING, SELECT가 끝난 뒤 마지막에 실행된다.&lt;br /&gt;그래서 ORDER BY는 &amp;ldquo;데이터를 찾는 문법&amp;rdquo;이라기보다 &amp;ldquo;조회된 결과를 보기 좋게 정리하는 문법&amp;rdquo;이라고 생각하면 편하다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️ 기본 정렬 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ORDER BY 뒤에는 정렬 기준이 되는 컬럼을 적는다&lt;br /&gt;이때 ASC는 오름차순, DESC는 내림차순을 의미한다 그리고 ASC는 생략 가능하며, 생략하면 기본값으로 오름차순이 적용된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;pre id=&quot;code_1774970237972&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT MOVIE_NAME, RATING
FROM MOVIE_INFO
ORDER BY RATING;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 쿼리는 평점을 기준으로 오름차순 정렬한다. 즉 값이 작은 것부터 큰 것 순서대로 출력된다.&lt;br /&gt;반대로 높은 점수부터 보고 싶다면 DESC를 붙이면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;pre id=&quot;code_1774970284740&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT MOVIE_NAME, RATING
FROM MOVIE_INFO
ORDER BY RATING DESC;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️ 여러 컬럼으로 정렬하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬 기준은 하나만 쓰는 것이 아니라 여러 개를 함께 줄 수 있다.&lt;br /&gt;이 경우 앞에 쓴 컬럼을 먼저 기준으로 정렬하고, 그 값이 같을 때 다음 컬럼 기준으로 다시 정렬한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;pre id=&quot;code_1774973275319&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT MOVIE_NAME, GENRE, RATING
FROM MOVIE_INFO
ORDER BY GENRE, RATING DESC;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;1564&quot; data-start=&quot;1384&quot; data-ke-size=&quot;size16&quot;&gt;여기서 중요한 점은 ORDER BY GENRE, RATING DESC가 &amp;ldquo;둘 다 내림차순&amp;rdquo;이라는 뜻이 아니라는 점이다.&lt;br /&gt;GENRE는 기본값인 오름차순이 적용되고, RATING만 내림차순이 적용된다. 따라서 각 컬럼마다 정렬 방향을 따로 생각해야 한다.&lt;/p&gt;
&lt;h3 data-end=&quot;1564&quot; data-start=&quot;1384&quot; data-ke-size=&quot;size23&quot;&gt;✔️ ORDER BY에서 사용할 수 있는 표현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ORDER BY는 꼭 컬럼명만 쓰는 문법이 아니다.&lt;br /&gt;일반 컬럼, 숫자, 별칭, 혼용 방식, CASE 문법까지 ORDER BY 뒤에 사용할 수 있다 따라서 정렬 기준을 생각보다 다양하게 줄 수 있다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️&amp;nbsp; 일반 컬럼으로 정렬&lt;/h3&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;pre id=&quot;code_1774973424732&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT MOVIE_NAME, TICKET_PRICE
FROM MOVIE_INFO
ORDER BY TICKET_PRICE ASC;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️ 숫자로 정렬&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ORDER BY에는 SELECT절에 적힌 컬럼 순서를 숫자로 적어서 정렬할 수도 있다.&lt;br /&gt;예시) SELECT에서 세 번째 컬럼이 RATING이라면 ORDER BY 3 DESC는 ORDER BY RATING DESC와 같은 의미다&lt;/p&gt;
&lt;pre id=&quot;code_1774973486749&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT MOVIE_NAME, GENRE, RATING
FROM MOVIE_INFO
ORDER BY 3 DESC;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이방법은 가독성이 떨어지고 컬럼순사가 바뀌면 의미가 달라질수도 있으니 숫자보다는 컬럼명이나 별칭을 쓰는게 좋다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️ 일반 컬럼, 숫자 혼용해서 정렬&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ORDER BY에서는 일반 컬럼, 숫자, 별칭을 섞어서도 사용할 수 있다.&lt;br /&gt;즉 한쪽은 숫자로, 다른 쪽은 컬럼명으로 정렬 조건을 줄 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1774973672451&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT MOVIE_NAME, GENRE, RATING
FROM MOVIE_INFO
ORDER BY 3 DESC, TICKET_PRICE;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 쿼리는 먼저 세 번째 컬럼인 RATING으로 내림차순 정렬하고, 같은 값이면 TICKET_PRICE로 다시 정렬한다&lt;br /&gt;가능은 하지만 읽는 사람이 헷갈릴 수 있어서, 웬만하면 한 가지 방식으로 통일하는 게 더 깔끔하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️ SELECT에서 만든 별칭으로 정렬&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ORDER BY는 SELECT보다 나중에 실행되기 때문에 SELECT절에서 만든 별칭도 정렬 기준으로 사용할 수 있다.&lt;br /&gt;WHERE에서는 별칭을 바로 못 쓰는 경우가 많은데, ORDER BY에서는 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;pre id=&quot;code_1774973754461&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT MOVIE_NAME, TICKET_PRICE * 1.1 AS PRICE_WITH_VAT
FROM MOVIE_INFO
ORDER BY PRICE_WITH_VAT DESC;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 쿼리는 부가세를 포함한 가격을 계산해서 별칭을 붙이고, 그 별칭을 기준으로 내림차순 정렬한 것이다.&lt;br /&gt;따라서 ORDER BY는 출력된 결과를 다루기 때문에 SELECT에서 만든 이름도 정렬 기준으로 사용할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️ ORDER BY에서 CASE 문법 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ORDER BY에서는 CASE 문법도 사용할 수 있다.&lt;br /&gt;단순 오름차순, 내림차순이 아니라 내가 원하는 우선순위를 직접 정해서 정렬할 때 유용하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;pre id=&quot;code_1774973844087&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT MOVIE_NAME, GENRE, RATING
FROM MOVIE_INFO
ORDER BY CASE GENRE
           WHEN 'SF' THEN 1
           WHEN '액션' THEN 2
           ELSE 3
         END,
         RATING DESC;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 쿼리는 장르가 SF면 1, 액션이면 2, 나머지는 3으로 바꿔서 정렬 기준을 만든다.&lt;br /&gt;SF를 가장 먼저, 액션을 그다음, 나머지를 마지막으로 두고, 같은 그룹 안에서는 평점 내림차순으로 정렬한다는 뜻이다. 여기서 중요한 점은 CASE 문이 실제 출력값을 바꾸는 것은 아니라는 점이다. 정렬 기준에만 영향을 주고, 결과에 표시되는 GENRE 값은 원래 값 그대로 유지된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️ ORDER BY에서 NULL 처리&lt;/h3&gt;
&lt;p data-end=&quot;4297&quot; data-start=&quot;4281&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;Oracle&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;4402&quot; data-start=&quot;4298&quot; data-ke-size=&quot;size16&quot;&gt;오름차순(ASC)일 때 NULL은 가장 뒤에 정렬된다.&lt;br /&gt;내림차순(DESC)일 때 NULL은 가장 앞에 정렬된다.&lt;/p&gt;
&lt;p data-end=&quot;4402&quot; data-start=&quot;4298&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; SQL Server&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;4402&quot; data-start=&quot;4298&quot; data-ke-size=&quot;size16&quot;&gt;오름차순(ASC)일 때 NULL은 가장 앞에 정렬된다.&lt;br /&gt;내림차순(DESC)일 때 NULL은 가장 뒤에 정렬된다.&lt;/p&gt;
&lt;p data-end=&quot;4402&quot; data-start=&quot;4298&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; Oracle의 NULLS FIRST / NULLS LAST&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;4402&quot; data-start=&quot;4298&quot; data-ke-size=&quot;size16&quot;&gt;Oracle에서는 NULL 정렬 위치를 명시적으로 지정할 수 있다.&lt;br /&gt;이때 사용하는 옵션이 NULLS FIRST, NULLS LAST다.&lt;/p&gt;
&lt;p data-end=&quot;4402&quot; data-start=&quot;4298&quot; data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;pre id=&quot;code_1774974250092&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT MOVIE_NAME, TICKET_PRICE
FROM MOVIE_INFO
ORDER BY TICKET_PRICE ASC NULLS FIRST;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 쿼리는 오름차순 정렬을 하되, NULL 값을 가장 위에 두겠다는 의미다. 따라서 Oracle 기본 동작과 다르게 NULL 위치를 직접 조정할 수 있다.&lt;/p&gt;
&lt;h3 data-end=&quot;5191&quot; data-start=&quot;5173&quot; data-ke-size=&quot;size23&quot;&gt;✔️ 핵심 정리&lt;/h3&gt;
&lt;p data-end=&quot;5517&quot; data-start=&quot;5193&quot; data-ke-size=&quot;size16&quot;&gt;ORDER BY는 조회 결과를 정렬하는 문법이고, SQL 실행 순서상 가장 마지막에 실행된다.&lt;/p&gt;
&lt;p data-end=&quot;5517&quot; data-start=&quot;5193&quot; data-ke-size=&quot;size16&quot;&gt;기본 정렬은 ASC, 내림차순은 DESC이며, 여러 컬럼으로 정렬할 때는 앞의 기준부터 순서대로 적용된다. 또 ORDER BY에는 컬럼명뿐 아니라 숫자, 별칭, CASE 문법도 사용할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;5517&quot; data-start=&quot;5193&quot; data-ke-size=&quot;size16&quot;&gt;그리고 NULL 정렬 방식은 DBMS마다 다르므로 Oracle과 SQL Server 차이를 구분해서 기억해야 한다. Oracle에서는 NULLS FIRST, NULLS LAST로 NULL 위치를 직접 지정할 수도 있다.&lt;/p&gt;</description>
      <category> 꼬부기의 성장서재/이기적SQLD 기출문제</category>
      <author>서화</author>
      <guid isPermaLink="true">https://kkobug2.tistory.com/255</guid>
      <comments>https://kkobug2.tistory.com/255#entry255comment</comments>
      <pubDate>Tue, 31 Mar 2026 22:54:14 +0900</pubDate>
    </item>
    <item>
      <title>GROUP BY, HAVING절</title>
      <link>https://kkobug2.tistory.com/254</link>
      <description>&lt;h3 data-end=&quot;107&quot; data-start=&quot;92&quot; data-ke-size=&quot;size23&quot;&gt;✔️ GROUP BY란&lt;/h3&gt;
&lt;p data-end=&quot;326&quot; data-start=&quot;109&quot; data-ke-size=&quot;size16&quot;&gt;GROUP BY는 특정 컬럼을 기준으로 데이터를 그룹화하고, 합계, 평균, 개수 같은 집계 결과를 구할 때 사용하는 문법이다. 쉽게 말하면 여러 행을 어떤 기준으로 묶어서 하나의 요약 결과로 바꿔주는 역할이다&lt;/p&gt;
&lt;h3 data-end=&quot;326&quot; data-start=&quot;109&quot; data-ke-size=&quot;size23&quot;&gt;✔️ GROUP BY 실행 흐름&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GROUP BY는 SQL 작성 순서만 보면 뒤에 나오지만, 실제 실행 순서에서는 FROM, WHERE 다음에 실행되고 SELECT보다 먼저 처리된다. 따라서 먼저 테이블을 정하고, 필요하면 WHERE로 조건을 걸러낸 다음 같은 값끼리 그룹을 만들어서&amp;nbsp; 마지막에 SELECT로 보여줄 결과를 정한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 GROUP BY를 이해할 때는 &amp;ldquo;행을 먼저 묶고, 그다음 집계 결과를 보여준다&amp;rdquo;는 순서로 생각하는 게 편하다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️ GROUP BY 기본 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시) 수강생 정보 테이블에서 소속반별 학생수 구하기&lt;/p&gt;
&lt;pre id=&quot;code_1774889818982&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT 소속반, COUNT(*) AS 반별인원수
FROM 수강생정보
GROUP BY 소속반;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소속반을 기준으로 같은 값끼리 먼저 묶고 각 그룹마다 COUNT(*) 개수를 센다 GROUP BY를 사용하면 여러 행을 하나의 그룹으로 요약해서 볼 수 있다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️ GROUP BY를 쓰면 행이 줄어드는 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GROUP BY를 사용하면 원본 테이블의 행 수 그대로 나오는 것이 아니라, 그룹 기준 컬럼의 종류 수만큼 결과가 줄어든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시) 학생이 9명이 있을때 소속반 종류가 A, B, C 세 개뿐이면 결과는 3행만 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 GROUP BY는 &amp;ldquo;원본 데이터 전체를 그대로 보여주는 문법&amp;rdquo;이 아니라 &amp;ldquo;같은 값을 묶어서 대표 결과만 보여주는 문법&amp;rdquo;이다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️ GROUP BY 사용 시 주의사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GROUP BY를 쓰면 SELECT절에는 GROUP BY에 명시한 컬럼, 표현식, 또는 집계 함수만 사용할 수 있다.&lt;br /&gt;그룹화가 끝난 뒤에는 이미 행이 줄어들었기 때문에 그룹 기준이 아닌 일반 컬럼은 어떤 값을 보여줘야 할지 결정할 수 없기 때문이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오류 나는 SQL예시&lt;/p&gt;
&lt;pre id=&quot;code_1774890161669&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT 소속반, 학생이름, COUNT(*)
FROM 수강생정보
GROUP BY 소속반;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소속반 기준으로는 그룹이 묶였지만, 각 그룹 안에 학생이 여러 명이 있으므로 학생이름에 어떤 값을 표시해야 할지 정할 수 없어서 오류가 발생한다 그래서 GROUP BY를 사용할 때는 &amp;ldquo;내가 SELECT에 쓴 컬럼이 그룹 기준인지, 아니면 집계 결과인지&amp;rdquo;를 꼭 확인해야 한다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️ GROUP BY에는 표현식도 사용할 수 있다&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️ GROUP BY와 함께 쓰는 집계 함수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GROUP BY는 보통 집계 함수와 같이 사용한다&amp;nbsp; 집계 함수는 여러 행을 입력받아 하나의 결과를 반환하는 함수다 대표적으로 COUNT, SUM, AVG, MAX, MIN이 있다.&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;&lt;b&gt; COUNT 함수&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;COUNT는 그룹별 행의 개수를 세는 함수다&amp;nbsp; 중요한 점은 COUNT()와 COUNT(컬럼)의 차이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;COUNT()는 NULL 여부와 상관없이 전체 행 수를 세고, COUNT(컬럼)은 해당 컬럼에서 NULL이 아닌 값만 센다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 COUNT는 같은 개수 세기 함수처럼 보여도 무엇을 기준으로 세느냐에 따라 결과가 달라질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;pre id=&quot;code_1774890557868&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT 학생ID,
       COUNT(*) AS 전체건수,
       COUNT(성적) AS 유효성적건수
FROM 성적표
GROUP BY 학생ID;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학생의 성적이 전부 NULL일때 COUNT(*)는 3이지만, COUNT(성적)은 0으로 나온다. 즉 전체 행은 3개 존재하지만 실제 값이 있는 성적은 없다는 뜻이다&lt;/p&gt;
&lt;p data-end=&quot;3047&quot; data-start=&quot;2920&quot; data-ke-size=&quot;size16&quot;&gt;또한 COUNT(*), COUNT(1), COUNT(0)도 모두 같은 의미다 셋 다 결국 NULL을 포함해 행 수를 센다고 이해하면 된다&lt;/p&gt;
&lt;p data-end=&quot;3047&quot; data-start=&quot;2920&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; SUM과 AVG 함수&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;3047&quot; data-start=&quot;2920&quot; data-ke-size=&quot;size16&quot;&gt;SUM은 그룹별 합계를 구하고, AVG는 그룹별 평균을 구하는 함수다&lt;/p&gt;
&lt;p data-end=&quot;3047&quot; data-start=&quot;2920&quot; data-ke-size=&quot;size16&quot;&gt;두 함수 모두 숫자형 데이터에만 사용할 수 있고, NULL 값은 무시하고 계산한다&lt;/p&gt;
&lt;p data-end=&quot;3047&quot; data-start=&quot;2920&quot; data-ke-size=&quot;size16&quot;&gt;다만 입력값이 전부 NULL이면 결과도 NULL이 된다 따라서 NULL이 하나 섞여 있다고 무조건 결과가 NULL이 되는 것은 아니고, 계산 가능한 값들끼리만 집계한 뒤 결과를 내는 방식이다&lt;/p&gt;
&lt;p data-end=&quot;3047&quot; data-start=&quot;2920&quot; data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;pre id=&quot;code_1774890756690&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT 학생ID,
       AVG(성적) AS 평균성적,
       SUM(성적) AS 성적합계
FROM 성적표
GROUP BY 학생ID;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 성적이 NULL이기 때문에 AVG와 SUM 결과도 NULL로 나온다. 반대로 값이 100, 70, NULL이라면 SUM은 NULL을 제외하고 170을 계산하고, AVG도 NULL을 제외한 두 개의 값만 가지고 평균을 구한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; MAX와 MIN 함수&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MAX는 최대값, MIN은 최소값을 구하는 함수다. 이 함수들도 NULL은 무시하고 집계한다&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;pre id=&quot;code_1774891631374&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT 학생ID,
       MAX(성적) AS 최고성적,
       MIN(성적) AS 최저성적
FROM 성적표
GROUP BY 학생ID;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우도 NULL 값은 무시되며, 전부 NULL이면 결과는 NULL이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MAX, MIN도 NULL을 만나면 멈춘다가 아니라 NULL을 빼고 계산한다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️ GROUP BY 없이 집계 함수 사용 가능할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GROUP BY 없이 집계 함수를 사용하면 테이블 전체를 하나의 그룹으로 간주해서 집계한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 전체 행 수를 세고 싶다면 그냥 COUNT(*)만 써도 된다. 이 경우는 &amp;ldquo;그룹을 안 나눈다&amp;rdquo;가 아니라 전체를 하나의 그룹으로 묶는다고 이해하면 편하다&lt;/p&gt;
&lt;pre id=&quot;code_1774892202627&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT COUNT(*)
FROM 성적표;&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;예를 들어 학생ID와 COUNT(*)를 같이 쓰고 싶다면 학생ID로 그룹화해야 한다 그렇지 않으면 전체를 하나의 그룹으로 본 상태에서 학생ID를 어떤 값으로 표시해야 할지 알 수 없어서 오류가 난다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️ 공집합과 집계 함수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건에 맞는 행이 하나도 없는 공집합 상황도 시험에서 자주 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 컬럼 조회는 결과가 0행으로 끝나지만, 집계 함수는 보통 NULL을 반환한다. 단 COUNT는 예외적으로 0을 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 공집합을 만났을 때 &amp;ldquo;행이 아예 없는 결과&amp;rdquo;와 &amp;ldquo;집계 결과 한 줄이 나오는데 값이 NULL인 경우&amp;rdquo;를 구분해서 봐야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️ HAVING절이란?&lt;/h3&gt;
&lt;p data-end=&quot;5311&quot; data-start=&quot;5097&quot; data-ke-size=&quot;size16&quot;&gt;HAVING은 GROUP BY로 그룹화한 결과에 조건을 거는 절이다&lt;br /&gt;따라서 WHERE는 그룹화 전에 개별 행을 필터링하고, HAVING은 그룹화 후 집계 결과를 기준으로 다시 한 번 거르는 역할을 한다. 쉽게 말하면 WHERE는 원본 데이터에 대한 조건, HAVING은 집계 결과에 대한 조건이다&lt;/p&gt;
&lt;p data-end=&quot;5488&quot; data-start=&quot;5313&quot; data-ke-size=&quot;size16&quot;&gt;예시) 반별 인원수를 구한 뒤, 그중 인원수가 3명 이상인 반만 보고 싶다면 HAVING을 사용한다. 이때 COUNT(*) 같은 집계 함수 결과를 조건으로 걸 수 있다.&lt;/p&gt;
&lt;h3 data-end=&quot;5488&quot; data-start=&quot;5313&quot; data-ke-size=&quot;size23&quot;&gt;✔️ WHERE와 HAVING 차이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WHERE는 GROUP BY 전에 실행되기 때문에 개별 행 단위 조건에 사용한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 HAVING은 GROUP BY 이후 실행되기 때문에 COUNT, SUM, AVG 같은 집계 결과를 조건으로 걸 때 사용한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시) 급여가 3000 이상인 직원만 먼저 뽑고 싶으면 WHERE를 쓰고, 부서별 평균 급여가 4000 이상인 부서만 보고 싶으면 HAVING을 써야 한다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔️ 핵심 정리&lt;/h3&gt;
&lt;p data-end=&quot;5488&quot; data-start=&quot;5313&quot; data-ke-size=&quot;size16&quot;&gt;GROUP BY는 데이터를 특정 기준으로 묶는 문법이고, HAVING은 그렇게 묶인 결과에 조건을 거는 문법이다&lt;/p&gt;
&lt;p data-end=&quot;5488&quot; data-start=&quot;5313&quot; data-ke-size=&quot;size16&quot;&gt;GROUP BY를 쓰면 SELECT에는 그룹 기준 컬럼이나 집계 함수만 올 수 있다는 점을 기억해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;5488&quot; data-start=&quot;5313&quot; data-ke-size=&quot;size16&quot;&gt;COUNT는 NULL 포함 여부에 따라 결과가 달라질 수 있고, SUM, AVG, MAX, MIN은 NULL을 무시하지만 전부 NULL이면 결과도 NULL이 된다.&lt;/p&gt;
&lt;p data-end=&quot;5488&quot; data-start=&quot;5313&quot; data-ke-size=&quot;size16&quot;&gt;또 GROUP BY 없이 집계 함수를 쓰면 전체를 하나의 그룹으로 본다는 점, 공집합에서 COUNT는 0이고 다른 집계 함수는 NULL이 될 수 있다는 점도 같이 정리해두면 좋다.&lt;/p&gt;</description>
      <category> 꼬부기의 성장서재/이기적SQLD 기출문제</category>
      <author>서화</author>
      <guid isPermaLink="true">https://kkobug2.tistory.com/254</guid>
      <comments>https://kkobug2.tistory.com/254#entry254comment</comments>
      <pubDate>Mon, 30 Mar 2026 22:28:15 +0900</pubDate>
    </item>
    <item>
      <title>WHERE절</title>
      <link>https://kkobug2.tistory.com/253</link>
      <description>&lt;h3 data-end=&quot;96&quot; data-start=&quot;82&quot; data-section-id=&quot;3e3wtn&quot; data-ke-size=&quot;size23&quot;&gt;✔️ WHERE절이란?&lt;/h3&gt;
&lt;p data-end=&quot;311&quot; data-start=&quot;98&quot; data-ke-size=&quot;size16&quot;&gt;WHERE절은 테이블에서 내가 원하는 행만 조건에 맞게 걸러서 조회할 수 있게 해주는 SQL 문법이다.&lt;br /&gt;SELECT, FROM과 함께 자주 사용되고, 실무에서도 정말 많이 쓰이는 기본 문법이다. 결국 전체 데이터를 다 보는 게 아니라, 필요한 데이터만 골라서 가져오기 위해 사용하는 절이라고 보면 된다.&lt;/p&gt;
&lt;h3 data-section-id=&quot;q21ps&quot; data-end=&quot;338&quot; data-start=&quot;318&quot; data-ke-size=&quot;size23&quot;&gt;✔️ WHERE절이 필요한 이유&lt;/h3&gt;
&lt;p data-end=&quot;590&quot; data-start=&quot;340&quot; data-ke-size=&quot;size16&quot;&gt;WHERE절이 필요한 이유는 불필요한 데이터를 줄이고, 원하는 결과만 빠르게 조회하기 위해서다.&lt;br /&gt;예를 들어 유저 정보가 100만 건 있는 테이블에서 특정 한 사람의 정보만 찾고 싶다면, WHERE 없이 전체를 다 보는 방식은 비효율적이다. 반면 WHERE를 사용하면 조건에 맞는 데이터만 바로 추출할 수 있다&lt;/p&gt;
&lt;h3 data-section-id=&quot;px39r2&quot; data-end=&quot;620&quot; data-start=&quot;597&quot; data-ke-size=&quot;size23&quot;&gt;✔️ SQL 실행 순서와 WHERE절&lt;/h3&gt;
&lt;p data-end=&quot;764&quot; data-start=&quot;622&quot; data-ke-size=&quot;size16&quot;&gt;SQL은 작성 순서와 실제 실행 순서가 다르다.&lt;br /&gt;보통 우리는 SELECT &amp;rarr; FROM &amp;rarr; WHERE 순서로 적지만, 실제 실행은 FROM &amp;rarr; WHERE &amp;rarr; GROUP BY &amp;rarr; HAVING &amp;rarr; SELECT &amp;rarr; ORDER BY 순서로 진행된다.&lt;/p&gt;
&lt;p data-end=&quot;951&quot; data-start=&quot;766&quot; data-ke-size=&quot;size16&quot;&gt;이 부분이 중요한 이유는 WHERE절이 먼저 조건에 맞는 행을 걸러낸 뒤, 마지막에 SELECT가 필요한 컬럼만 보여준다 따라서 SQL은 먼저 테이블을 가져오고, 그 다음 조건으로 데이터를 줄이고, 마지막에 보여줄 컬럼을 선택하는 흐름이다&lt;/p&gt;
&lt;h3 data-end=&quot;951&quot; data-start=&quot;766&quot; data-ke-size=&quot;size23&quot;&gt;✔️ WHERE절 기본 사용 방식&lt;/h3&gt;
&lt;pre id=&quot;code_1774755991693&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT 컬럼명
FROM 테이블명
WHERE 조건식;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시로 개발팀 소속 직원만 조회하고 싶다면&lt;/p&gt;
&lt;pre id=&quot;code_1774756032338&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT EMP_ID, EMP_NAME, DEPT
FROM EMPLOYEE
WHERE DEPT = '개발팀';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 작성할수 있다 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이 쿼리는 EMPLOYEE 테이블에서 전체 데이터를 가져오는 것이 아니라, DEPT가 '개발팀'인 행만 먼저 찾고, 그중에서 EMP_ID, EMP_NAME, DEPT 컬럼만 보여준다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;✔️ 비교 연산자&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WHERE절에서는 비교 연산자를 사용해서 조건을 줄 수 있다.&lt;br /&gt;=, !=, &amp;gt;, &amp;lt;, &amp;gt;=, &amp;lt;= 같은 기호를 사용한다. 이 연산자들은 값이 조건에 맞는지 비교할 때 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시) 급여가 5000이상인 직원만 조회하기&lt;/p&gt;
&lt;pre id=&quot;code_1774756148669&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT EMP_ID, EMP_NAME, SALARY
FROM EMPLOYEE
WHERE SALARY &amp;gt;= 5000;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SALARY가 5000 이상인 데이터만 먼저 걸러지고, 그 결과에 맞는 값이 출력된다&amp;nbsp; 여기서도 핵심은 WHERE가 먼저 조건에 맞는 행을 고른 뒤, SELECT가 보여줄 컬럼을 정한다&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;✔️ 논리 연산자 AND, OR&lt;/b&gt;&lt;/h4&gt;
&lt;p data-end=&quot;2033&quot; data-start=&quot;1973&quot; data-ke-size=&quot;size16&quot;&gt;AND는 앞의 조건과 뒤의 조건이 모두 참일 때만 결과를 반환한다&lt;/p&gt;
&lt;p data-end=&quot;2033&quot; data-start=&quot;1973&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AND&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;2033&quot; data-start=&quot;1973&quot; data-ke-size=&quot;size16&quot;&gt;예시) 개발팀이면서 여성인 직원만 조회하기&lt;/p&gt;
&lt;pre id=&quot;code_1774756313820&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT EMP_ID, EMP_NAME, DEPT, SALARY
FROM EMPLOYEE
WHERE DEPT = '개발팀'
  AND GENDER = '여';&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;&lt;b&gt;OR&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OR는 여러 조건 중 하나라도 참이면 결과를 반환한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시) 영업팀 또는 마케팅팀 직원 조회하기&lt;/p&gt;
&lt;pre id=&quot;code_1774756487198&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT EMP_ID, EMP_NAME, DEPT
FROM EMPLOYEE
WHERE DEPT = '영업팀'
   OR DEPT = '마케팅팀';&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;&lt;b&gt;  AND와 OR의 우선순위&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AND는 OR보다 우선순위가 높다. 그래서 괄호 없이 같이 쓰면 AND가 먼저 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;pre id=&quot;code_1774756578767&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT EMP_ID, DEPT, SALARY
FROM EMPLOYEE
WHERE DEPT = '인사팀'
   OR DEPT = '개발팀'
  AND SALARY &amp;gt;= 4000;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 쿼리는 &amp;ldquo;인사팀 또는 개발팀이면서 급여가 4000 이상&amp;rdquo;처럼 보일 수 있지만, 실제로는 AND가 먼저 실행된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해석하기&lt;/p&gt;
&lt;pre id=&quot;code_1774756641619&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;WHERE DEPT = '인사팀'
   OR (DEPT = '개발팀' AND SALARY &amp;gt;= 4000)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;3201&quot; data-start=&quot;3078&quot; data-ke-size=&quot;size16&quot;&gt;그래서 개발팀이면서 급여 4000 이상인 직원과, 인사팀 전체가 같이 조회된다. 이 때문에 인사팀인데 급여가 4000 미만인 직원도 포함될 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;3266&quot; data-start=&quot;3203&quot; data-ke-size=&quot;size16&quot;&gt;내가 원하는 조건이 &amp;ldquo;인사팀 또는 개발팀 중에서, 급여가 4000 이상인 사람&amp;rdquo;이라면 괄호로 먼저 묶어줘야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1774756679071&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT EMP_ID, DEPT, SALARY
FROM EMPLOYEE
WHERE (DEPT = '인사팀' OR DEPT = '개발팀')
  AND SALARY &amp;gt;= 4000;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건이 여러 개 섞이면 SQL이 어떤 순서로 해석하는지 확인해야 하고 괄호를 써서 명확하게 표현하는 게 좋다&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;✔️ 부정 연산자 NOT&lt;/b&gt;&lt;/h4&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;pre id=&quot;code_1774756856671&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT EMP_ID, EMP_NAME, DEPT
FROM EMPLOYEE
WHERE DEPT != '개발팀';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게하면 개발팀을 제외한 모든 팀의 직원들이 조회된다&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1774764571051&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;WHERE DEPT != '개발팀'
WHERE DEPT &amp;lt;&amp;gt; '개발팀'
WHERE DEPT ^= '개발팀'
WHERE NOT DEPT = '개발팀'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표시는 달라도 결국 다 다같지 않다는 의미다&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;✔️ NULL 연산자&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NULL은 값이 비어 있는 상태를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NULL은 일반 값처럼 =으로 비교할 수 없다. 그래서 NULL 전용 연산자인 IS NULL, IS NOT NULL을 사용해야 한다.&lt;/p&gt;
&lt;p data-section-id=&quot;sivfak&quot; data-end=&quot;4251&quot; data-start=&quot;4235&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;IS NULL&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;4327&quot; data-start=&quot;4253&quot; data-ke-size=&quot;size16&quot;&gt;값이 비어 있는 데이터를 조회할 때 사용한다.&lt;br /&gt;예시) 퇴사일이 없는 아직 재직 중인 직원을 조회하기&lt;/p&gt;
&lt;pre id=&quot;code_1774765240835&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT EMP_ID, EMP_NAME, RETIRE_DATE
FROM EMPLOYEE
WHERE RETIRE_DATE IS NULL;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-section-id=&quot;mipb7e&quot; data-end=&quot;4534&quot; data-start=&quot;4514&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;IS NOT NULL&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;4607&quot; data-start=&quot;4536&quot; data-ke-size=&quot;size16&quot;&gt;값이 들어 있는 데이터를 조회할 때 사용한다.&lt;br /&gt;예시) 퇴사한 직원만 보기&lt;/p&gt;
&lt;pre id=&quot;code_1774765311784&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT EMP_ID, EMP_NAME, RETIRE_DATE
FROM EMPLOYEE
WHERE RETIRE_DATE IS NOT NULL;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-section-id=&quot;9b2xmx&quot; data-end=&quot;4823&quot; data-start=&quot;4792&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;NULL 조건과 다른 조건 함께 사용하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;4901&quot; data-start=&quot;4825&quot; data-ke-size=&quot;size16&quot;&gt;NULL 조건도 다른 조건과 함께 사용할 수 있다.&lt;br /&gt;예시) 재직 중이면서 급여가 5000 이상인 직원을 조회하기&lt;/p&gt;
&lt;pre id=&quot;code_1774765432946&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT EMP_ID, EMP_NAME, RETIRE_DATE, SALARY
FROM EMPLOYEE
WHERE RETIRE_DATE IS NULL
  AND SALARY &amp;gt;= 5000;&lt;/code&gt;&lt;/pre&gt;</description>
      <category> 꼬부기의 성장서재/이기적SQLD 기출문제</category>
      <author>서화</author>
      <guid isPermaLink="true">https://kkobug2.tistory.com/253</guid>
      <comments>https://kkobug2.tistory.com/253#entry253comment</comments>
      <pubDate>Sun, 29 Mar 2026 15:07:19 +0900</pubDate>
    </item>
  </channel>
</rss>