<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>summeryoung_.log</title>
        <link>https://velog.io/</link>
        <description>이불 밖은 위험해.</description>
        <lastBuildDate>Sat, 04 Apr 2026 09:20:27 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>summeryoung_.log</title>
            <url>https://velog.velcdn.com/images/summeryoung_/profile/b2b943f3-68e2-43fd-8838-78ddb0dd8635/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. summeryoung_.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/summeryoung_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[데이터베이스 5주차 SQL 문제 풀이]]></title>
            <link>https://velog.io/@summeryoung_/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-5%EC%A3%BC%EC%B0%A8-SQL-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4</link>
            <guid>https://velog.io/@summeryoung_/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-5%EC%A3%BC%EC%B0%A8-SQL-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4</guid>
            <pubDate>Sat, 04 Apr 2026 09:20:27 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h4 id="q1-클래스-문제1-거래-상태에-따른-출력-및-날짜-비교">Q1. 클래스 문제1: 거래 상태에 따른 출력 및 날짜 비교</h4>
</blockquote>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/8e5fdbee-3409-4c33-9e32-c2aed313e49d/image.png" alt=""></p>
<pre><code class="language-sql">SELECT board_id, writer_id, title, price,
       CASE status
        WHEN &#39;SALE&#39; THEN &#39;판매중&#39;
        WHEN &#39;RESERVED&#39; THEN &#39;예약중&#39;
        WHEN &#39;DONE&#39; THEN &#39;거래완료&#39;
        ELSE status
       END AS STATUS
FROM used_goods_board
WHERE created_date = &#39;2022-10-05&#39;
ORDER BY board_id DESC;</code></pre>
<p>중고 거래 게시글 중에서 생성일자를 기준으로 한 번 거르고, 거래 상태에 따라 판매중/예약중/거래완료를 표기하는 문제였다.</p>
<p>날짜를 비교하는 부분은 날짜 그대로 비교하는게 성능적으로 더 좋다기에 <code>created_date = &#39;2022-10-05&#39;</code>와 같이 날짜 그대로 두고 비교하였다.</p>
<p>또 거래 상태가 SALE/RESERVED/DONE일 때에 따라 다른 식으로 표기해줘야하는 부분은 <code>CASE WHEN</code>을 사용했다. <code>WHEN</code> 뒤에 조건식을 써도 되지만, 단일값의 경우가 동일한지 (<code>=</code> 이 연산 비교) 에는 C언어나 다른 언어의 <code>switch</code>문처럼 CASE에 속성을 적고, WHEN 뒤에 값을 적어주면 되기에 그렇게 작성해주었다. </p>
<hr>
<blockquote>
<h4 id="q2-클래스-문제2-10월-생성된-글-및-댓글-조회">Q2. 클래스 문제2: 10월 생성된 글 및 댓글 조회</h4>
</blockquote>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/7134880d-ba94-4e51-8300-ad7c41d2cc58/image.png" alt=""></p>
<pre><code class="language-sql">SELECT B.title, B.board_id, 
       R.reply_id, R.writer_id, R.contents, 
       DATE_FORMAT(R.created_date, &#39;%Y-%m-%d&#39;)
FROM used_goods_board B JOIN
     used_goods_reply R ON
     B.board_id = R.board_id
WHERE MONTH(B.created_date) = &#39;10&#39;
ORDER BY R.created_date ASC, B.title;</code></pre>
<p>댓글 테이블과 게시글 테이블이 있었고, 댓글 테이블에서 게시글 id를 외래키로 가지면서 관계를 맺고 있는 식이었다.</p>
<p>10월에 만들어진 게시글과 그 댓글 등의 정보를 조회해서 출력하는 문제여서 일단은 <code>MONTH()</code>를 써서 비교했는데...? 이게 연도 조건이 있었던걸로 기억하는데 이렇게 풀면 연도는 사실 상관없이 10월에 만든 글이면 다 출력될텐데...? 어떻게 통과가 된 것 같다.</p>
<p>실제로는 <code>WHERE MONTH(B.created_date) = &#39;10&#39;</code> 이런식으로 쓰지 않고 그래서
<code>B.created_date BEWTEEN &#39;2022-10-01&#39; AND &#39;2022-10-31&#39;</code> 이렇게 쓰는게 더 좋을 것 같다. 또 찾아보니까, <code>B.created_date LIKE &#39;2022-10%&#39;</code> 이렇게 써도 10월인 날짜들만 걸러지는 것 같은데 이건 문자열로 비교하는거라 성능이 별로...다.</p>
<hr>
<blockquote>
<h4 id="q3-클래스-문제3">Q3. 클래스 문제3:</h4>
</blockquote>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/6c19d450-30a2-45d1-bb33-e9449c779fa5/image.png" alt=""></p>
<pre><code class="language-sql">-- 코드를 입력하세요
SELECT B.book_id, A.author_name, 
       DATE_FORMAT(B.published_date, &#39;%Y-%m-%d&#39;) AS PUBLISHED_DATE
FROM book B JOIN
     author A ON
     B.author_id = A.author_id
WHERE B.category =  &#39;경제&#39;
ORDER BY B.published_date;</code></pre>
<p>크게 복잡한건 없었고, 테이블 두 개를 JOIN으로 잘 연결한 후에 카테고리가 경제인 것들만 남기고, 날짜 형식만 요구대로 잘 조정해주었다.</p>
<p>날짜 형식은 <code>DATE_FORMAT()</code> 사용해서 연월일만 출력하도록 조정해주었다. </p>
<hr>
<blockquote>
<h4 id="q4-클래스-문제4-상품의-코드-앞-자리-두-개를-기준으로-분류">Q4. 클래스 문제4: 상품의 코드 앞 자리 두 개를 기준으로 분류</h4>
</blockquote>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/5175eda2-247e-40e5-86b9-7372b721a1c2/image.png" alt=""></p>
<pre><code class="language-sql">SELECT LEFT(product_code,2) AS category, 
       COUNT(DISTINCT product_id) AS product
FROM product P
GROUP BY category
ORDER BY category;</code></pre>
<p>처음에는 순간 앞 자리 두 개로 어떻게 분류해야하지? 했고, 약간 테이블을 하나 만들거나 서브 쿼리를 만들어야하나 생각했는데, <code>LEFT()</code>를 써서 분리하고 이걸 기준으로 그루핑하고, 정렬하면 되는 것이었다. 정렬은 SELECT 후에 진행되니 SELECT에서 만든 별칭으로 정렬해도 되고.</p>
<p><code>LEFT(속성,2)</code> 이렇게 해서 우선 상품 코드의 앞 두 개를 카테고리로 SELECT에서 정의해주었다.</p>
<p>그리고 위에서는 GROUP BY에 별칭 사용하긴 했는데 MySQL이나 MariaDB에서는 이렇게 GROUP BY에서 SELECT에서 사용한 별칭을 사용하는걸 허용하는데 Oracle에서는 허용하지 않는다고 하니 만약에 표준적으로 사용해야하는걸 생각하면</p>
<pre><code class="language-sql">GROUP BY LEFT(product_code,2)</code></pre>
<p>이렇게 적어야할 것 같았는데, Oracle에서는 LEFT도 없다고 하니? 그것조차 SUBSTR으로 바꾸어 주어야할 것 같다...</p>
<hr>
<blockquote>
<h4 id="q5-클래스-문제5-상품-출고-날짜를-기준으로-출력">Q5. 클래스 문제5: 상품 출고 날짜를 기준으로 출력</h4>
</blockquote>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/daa05c23-c4c9-4880-9242-79af79684b11/image.png" alt=""></p>
<pre><code class="language-sql">SELECT order_id, product_id, COALESCE(out_date,&#39; &#39;) AS out_date,
        CASE
        WHEN out_date &lt;= &#39;2022-05-01&#39; THEN &#39;출고완료&#39;
        WHEN out_date IS NULL THEN &#39;출고미정&#39;
        ELSE &#39;출고대기&#39;
        END AS &#39;출고여부&#39;
FROM food_order
ORDER BY order_id;</code></pre>
<p>날짜 기준으로 출고 여부를 잘 출력해줘야하는 문제였는데, 일단 처음에 위에처럼 쿼리를 작성해서 여러번 틀렸다.</p>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/e985a20c-3687-4335-934f-4b1ecccdea44/image.png" alt=""></p>
<pre><code class="language-sql">SELECT order_id, product_id, DATE_FORMAT(out_date, &#39;%Y-%m-%d&#39;) AS out_date,
        CASE
        WHEN out_date &lt;= &#39;2022-05-01&#39; THEN &#39;출고완료&#39;
        WHEN out_date IS NULL THEN &#39;출고미정&#39;
        WHEN out_date &gt; &#39;2022-05-01&#39; THEN &#39;출고대기&#39;
        END AS &#39;출고여부&#39;
FROM food_order
ORDER BY order_id;</code></pre>
<p>코드를 바꿔보면서 위처럼 작성해야하는걸 깨달았는데. 일단 CASE WHEN으로 구분하는건 괜찮았고, NULL일 때 출고 미정인 부분들은 틀린게 없었는데 <code>out_date</code> 출력할 때 문제가 있었다.</p>
<ol>
<li>널 값을 굳이 다른 값으로 채울 필요가 없었다.</li>
<li>날짜 형식 지정을 잘 해줘야했다.</li>
</ol>
<p>처음에는 <code>COALESCE</code>를 사용해서 널인 경우에는 공백을 출력하도록 해줘야할 것 같아 그렇게 했는데 이렇게 하면 DATE_FORMAT을 하지 않아도 한 것처럼 연월일이 출력되어서 계속 그대로 돌렸는데, 일단 포맷 형식을 지정해줘야했었고, 공백을 출력하게 지정하지 않아도 됐다. 널은 왠지 널이라고 쓰여있어야할 것 같아서 그냥 공백이길래 &#39; &#39; 이걸 지정해줘서 문제였다.</p>
<p>다른게 문제인줄 알고 바꾸다가 CASE절도 바꾸어줬는데 마지막에 출고 대기는 그냥 전처럼 ELSE를 쓰는게 나을 것 같기도하다.</p>
<hr>
<blockquote>
<h4 id="q6-음식-종류별-즐겨찾기가-가장-많은-식당-출력">Q6. 음식 종류별 즐겨찾기가 가장 많은 식당 출력</h4>
</blockquote>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/f127983d-d0e5-4e62-9dfa-f8b1139d307f/image.png" alt=""></p>
<pre><code class="language-sql">SELECT food_type, rest_id, rest_name, favorites
FROM rest_info R1
WHERE favorites &gt;= (SELECT MAX(favorites)
                 FROM rest_info R2
                 WHERE R1.food_type = R2.food_type)
ORDER BY food_type DESC;</code></pre>
<p>이 문제가 음식 종류별로 이제 즐겨찾기가 가장 많은 행(식당)을 찾고 -&gt; 그 행(식당)의 정보를 출력하는 거였는데 이 즐겨찾기가 가장 많은 행은 <code>MAX()</code>로 찾는다해도, 그것과 비교를하려면 임시 테이블을 둬야할지 서브쿼리를 써야할 지 고민했다. 그리고 윈도우 함수 써서 rank 매기는 것도 방법일 것 같았다. (근데 서브쿼리가 훨씬 나을 것 같았다. 행 몇 개 두는 테이블을 만들 필요는 없을 것 같았다.)</p>
<p>WHERE절에 서브쿼리를 두었는데 (중첩질의) 처음에는 어떻게 <strong>조건에 부합하는 행(종류별 즐겨찾기가 가장 많은 그 행)만 뽑아낼 수 있을까</strong> 고민을 많이 했다.</p>
<p>상관질의 사용해서 상위 쿼리의 음식 종류와 하위 쿼리 음식 종류가 같은 것들로 1차로 조건 설정을 해주고, 그 안에서 이제 즐겨찾기 수를 토대로 걸러주면 됐다. 처음에는 <code>ALL</code>을 써서 했는데 없이 비교문만 사용해도 잘 돌아갔다.</p>
<p>하위 쿼리에서 해당 음식 종류의 최대 즐겨찾기 수를 찾고 그것과 상위 쿼리의 즐겨찾기 수를 행마다 비교하며 각 음식 종류의 즐겨찾기 수가 가장 많은 음식점 정보를 잘 출력하면 됐다.</p>
<hr>
<blockquote>
<h4 id="q7-클래스-문제7-렌트카-대여-가능-여부-출력">Q7. 클래스 문제7: 렌트카 대여 가능 여부 출력</h4>
</blockquote>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/899aa9d7-3538-4013-af31-4a3bd03ec559/image.png" alt=""></p>
<pre><code class="language-sql">SELECT car_id,
        MAX(CASE 
            WHEN &#39;2022-10-16&#39; BETWEEN start_date AND end_date
            THEN &#39;대여중&#39;
            ELSE &#39;대여 가능&#39;
        END) AS availability
FROM car_rental_company_rental_history
GROUP BY car_id
ORDER BY car_id DESC;</code></pre>
<p>차량 대여 기록이 엄청 많고, 빌렸던걸 또 빌리고 막 하는 기록들이 많아서 막상 테이블을 출력해보면 동일한 아이디가 대여중/대여 가능이 번갈아서 막 여러 번 나왔었다.</p>
<p>이걸 동일한 ID의 여러 기록들/값들 중에 어떻게 걸러내야하지? 라는 생각이 들었다. 그루핑하자는 생각이 들었는데 이 경우에는 대여중/대여 가능을 어떻게 선택할 조건을 설정할 수 없으니 랜덤하게 나와버린다.</p>
<p><code>car_id</code>를 기준으로 그룹화를 하고, SELECT절에서 CASE WHEN을 사용해 날짜 기준으로 조건의 날짜가 start_date나 end_date 사이에 있으면 대여중, 아니면 대여 가능을 반환하도록 했는데, 위의 문제는 해결하지 못했다.</p>
<p>이거 관련해서 AI한테 물어봤을 때는 &#39;대여중&#39; &#39;대여 가능&#39; 중에서 대여 중이 MAX를 썼을 때 더 크니까(뒤) 그렇게 하면 그루핑 후에 대여중+대여가능이 모두 뜰 경우 대여중으로 설정된다고 한다. 그래서 기존에 썼던 코드에서 SELECT의 CASE WHEN을 MAX로 감쌌는데 잘 돌아갔다.</p>
<p>약간 꼼수같은 방법이라 다른 사람들 풀이가 궁금해서 찾아봤는데 CASE WHEN 안에서 또 SELECT를 사용해서 IN으로 이제 하나라도 start_date와 end_date 사이에 해당 날짜가 끼여있는지 확인하면 해결하면 되는 것 같았다.</p>
<p>아래는 다시 풀었을 때...인데 의외로 또 간단하다.</p>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/1192f3e1-d180-44f3-87ec-1b5264000d25/image.png" alt=""></p>
<pre><code class="language-sql">SELECT car_id,
       CASE
            WHEN car_id IN (SELECT car_id
                            FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
                            WHERE &#39;2022-10-16&#39; 
                                   BETWEEN start_date AND end_date )
            THEN &#39;대여중&#39;
            ELSE &#39;대여 가능&#39;
       END AS availability
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY C
GROUP BY car_id
ORDER BY car_id DESC;</code></pre>
<hr>
<blockquote>
<h4 id="q8-클래스-문제8-1월-판매된-작가별-카테고리별-총액-출력">Q8. 클래스 문제8: 1월 판매된 작가별-카테고리별 총액 출력</h4>
</blockquote>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/0c39f65b-3c01-41bd-b11f-1e85928ef034/image.png" alt=""></p>
<pre><code class="language-sql">WITH sales AS (
    SELECT A.author_id, A.author_name,
       B.category,
       SUM(S.sales)*B.price AS total_sales
    FROM book_sales S JOIN
     book B ON S.book_id = B.book_id JOIN
     author A ON A.author_id = B.author_id
    WHERE MONTH(S.sales_date) = &#39;1&#39;
    GROUP BY B.category, S.book_id
)

SELECT author_id, author_name,
       category,
       SUM(total_sales) AS total_sales
FROM sales
GROUP BY author_id, category
ORDER BY author_id, category DESC;</code></pre>
<p>위에서는 CTE 사용해서 풀어보긴했는데 사실 안써도 풀 수 있는 문제였다.</p>
<p>일단은 작가와 카테고리 별로 묶어서 총 판매액을 출력하면 되는 문제였는데 그룹화를 어떤 기준으로 할 지 고민했었다.</p>
<p>이 문제에서도 위에처럼 일단 월 설정하는거는 다시해야할 것 같다.</p>
<p>일단은 위에 문제를 풀 때는 카테고리별로 묶고, 그 안에서 책의 아이디 별로 묶어서 해당 책이 얼마나 팔렸는지를 먼저 기록하고, 그걸 토대로 다시 작가 아이디, 카테고리별로 묶었는데 이거를 사실 그냥 <code>GROUP BY author_id, category, book_id</code>로 하면 됐다.</p>
<p>근데 이걸 처음에 했다가 위처럼 테이블을 하나 더 만든 이유는 <code>SUM()</code>을 사용해서 계산할 때, <code>book_id</code> 별로 다른 가격을 반영해서 총액 계산하는게 어려워서 였다.</p>
<p><code>SUM(S.sales)</code> 이렇게 해서 판매한 개수를 얻어오고 이 뒤에 곱셈을 해서 다른 가격 반영하는게 쉽지 않았는데 <code>SUM(S.sales*B.price)</code> 이런식으로? <code>SUM()</code> 안에 하나가 아니라 수식을 넣어도 되는걸 다 풀고 찾아보다 알았다....</p>
<p>다시 풀게 되면 아래처럼 풀 것 같다.
<img src="https://velog.velcdn.com/images/summeryoung_/post/a70fb3e5-5d10-47b4-9482-2fe35c10937c/image.png" alt=""></p>
<pre><code class="language-sql">SELECT A.author_id, A.author_name, B.category,
       SUM(S.sales * B.price) AS total_sales
FROM book B JOIN
     book_sales S ON B.book_id = S.book_id JOIN
     author A ON B.author_id = A.author_id
WHERE S.sales_date BETWEEN &#39;2022-01-01&#39; AND &#39;2022-01-31&#39;
GROUP BY A.author_id, B.category
ORDER BY author_id, category DESC;</code></pre>
<p>약간 SUM을 하게되면, 그룹한것들을 총 합계를 구하는거니까 <code>SUM(S.sales*B.price)</code>를 하게 되면 이제 행별로 각각 곱한 후에 -&gt; 작가&amp;카테고리별로 묶었으니 그 기준으로 다 합하고 행 줄이기. 이런식으로 진행되는 것 같다.</p>
<hr>
<blockquote>
<h4 id="q9-클래스-문제9-게시글의-수가-3개-이상인-고객-정보-출력">Q9. 클래스 문제9: 게시글의 수가 3개 이상인 고객 정보 출력</h4>
</blockquote>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/78953fdc-98eb-4480-a0fb-2cf192593dd4/image.png" alt=""></p>
<pre><code class="language-sql">WITH users AS (
    SELECT U.user_id AS user_id
    FROM used_goods_board B JOIN
        used_goods_user U ON
        B.writer_id = U.user_id
    GROUP BY U.user_id
    HAVING COUNT(DISTINCT B.board_id) &gt;= 3)

SELECT U.user_id, U.nickname, 
       CONCAT(U.city,&#39; &#39;,U.street_address1,&#39; &#39;, U.street_address2) AS &#39;전체주소&#39;,
       CONCAT(LEFT(TLNO,3),&#39;-&#39;,SUBSTR(TLNO,4,4),&#39;-&#39;,RIGHT(TLNO,4)) AS &#39;전화번호&#39;
FROM used_goods_user U LEFT JOIN
     users ON 
     U.user_id = users.user_id
WHERE users.user_id IS NOT NULL
ORDER BY U.user_id DESC;</code></pre>
<p>위에도 CTE 사용하기는 하는데, 사실 그럴 필요는 없었을 것 같긴하다. 그냥 분리하면 아래를 더 간단하게 쓸 수 있을 것 같았다...?</p>
<p>일단 위에서 3번 이상 게시글을 올린 사용자의 아이디만 테이블에 남겼다. 그룹화하고, <code>HAVING</code> 사용해서 이 부분은 풀었다.</p>
<p>그 후에 그렇게 만든 테이블을 기존 테이블하고 조인한 뒤에 널값 아닌 사용자의 정보들만 출력했다. 사실 CTE 만드는건 불필요한데 이번주 내용에서 배운 거라 사용하고 조인도 사용해봤다. </p>
<p>실제로 다시 풀게되면 그냥 바로 GROUP BY -&gt; HAVING 쓴 뒤에 SELECT에서 조건대로 처리할 것 같다. 아래처럼. </p>
<p>문제에서 신경써서 처리할 부분은 <code>CONCAT()</code> 써서 요구조건대로 문자열 만드는 부분이었다. 전화 번호 사이에 &#39;-&#39; 넣는 것이나 주소 연결하는 것만 잘 처리해주었다. (시키는대로 하면 되는데 그 조건이 은근 자잘해서 귀찮고 또 완전 안똑같으면 틀려서 몇 번 다시 풀었다.)</p>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/ad23e531-fdc5-426b-8c6b-35ea54010307/image.png" alt=""></p>
<pre><code class="language-sql">SELECT U.user_id, U.nickname,
       CONCAT(U.city, &#39; &#39;, U.street_address1, &#39; &#39;, U.street_address2) AS &#39;전체주소&#39;,
       CONCAT(LEFT(U.tlno,3), &#39;-&#39;, SUBSTR(U.tlno,4,4), &#39;-&#39;, RIGHT(U.tlno,4)) AS &#39;전화번호&#39;
FROM used_goods_board B JOIN
     used_goods_user U ON B.writer_id = U.user_id
GROUP BY B.writer_id
HAVING COUNT(DISTINCT B.board_id) &gt;= 3
ORDER BY U.user_id DESC;</code></pre>
<p>CTE 만들지 않고 풀면 위와 같다. 이게 더 좋은 방법인 것 같긴하다, 위는 그냥 억지로 쓴 거...같고? 주소 연결할 때 공백을 빼먹어서 몇 번 다시 풀었었다....</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터베이스 5주차 내용 정리]]></title>
            <link>https://velog.io/@summeryoung_/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-5%EC%A3%BC%EC%B0%A8-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@summeryoung_/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-5%EC%A3%BC%EC%B0%A8-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sat, 04 Apr 2026 05:36:17 GMT</pubDate>
            <description><![CDATA[<h1 id="1-내장함수-null-비교문">1. 내장함수, NULL, 비교문</h1>
<h2 id="1-1-sql-내장함수">1-1. SQL 내장함수</h2>
<ul>
<li><strong>상수나 속성 이름</strong>을 입력값으로 받아 <strong>단일 값</strong>을 결과로 반환</li>
<li>모든 내장함수는 사용 시 유효한 입력값을 받아야함</li>
<li><strong>SELECT절, WHERE절, UPDATE절</strong>에서 모두 사용 가능</li>
</ul>
<hr>
<h3 id="1-숫자-함수">(1) 숫자 함수</h3>
<ul>
<li>SQL문에서 수학의 기본적인 사칙 연산자와 나머지 연산자 기호를 그대로 사용</li>
<li>MySQL은 이러한 연산자 중 사용 빈도가 높은 것을 <strong>내장 함수</strong> 형태로 제공</li>
</ul>
<blockquote>
<ul>
<li><strong>ABS(숫자)</strong>: 숫자의 절댓값을 계산</li>
</ul>
</blockquote>
<ul>
<li><strong>CEIL(숫자), FLOOR(숫자)</strong>: 올림/내림</li>
<li><strong>ROUND(숫자, m)</strong>: 숫자의 반올림. m은 기준수.</li>
</ul>
<h4 id="abs-함수">ABS 함수</h4>
<ul>
<li>절댓값 구하는 함수<pre><code class="language-sql">SELECT ABS(-78), ABS(78);</code></pre>
<img src="https://velog.velcdn.com/images/summeryoung_/post/f2bd879e-35e9-43c5-a5a6-c8e97c6256e0/image.png" width=80%>

</li>
</ul>
<hr>
<h4 id="round-함수">ROUND 함수</h4>
<ul>
<li>반올림한 값을 구하는 함수<pre><code class="language-sql">SELECT ROUND(4.12345, 3);</code></pre>
<img src="https://velog.velcdn.com/images/summeryoung_/post/e5b79ce9-1425-4677-97a6-74fe6fe4aafa/image.png" alt=""></li>
</ul>
<h4 id="숫자함수의-연산">숫자함수의 연산</h4>
<ul>
<li>숫자함수에는 <strong>직접 숫자를 입력</strong>하거나 <strong>열 이름을 사용</strong>할 수 있음</li>
<li>여러 함수를 <strong>복합적으로 사용</strong>할 수도 있음</li>
</ul>
<pre><code class="language-sql">SELECT custid &#39;고객번호&#39;, ROUND(SUM(saleprice)/COUNT(*), -2) &#39;평균금액&#39;
FROM orders
GROUP BY custid;</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/8eb02322-5261-4695-a999-3012a88dcf37/image.png" alt=""></p>
<hr>
<h3 id="2-문자함수">(2) 문자함수</h3>
<blockquote>
<h4 id="문자값-반환-함수">문자값 반환 함수</h4>
</blockquote>
<ul>
<li><strong>CONCAT(s1, s2)</strong>: 두 문자열을 연결</li>
<li><strong>LOWER(s), UPPER(s)</strong>: 대상 문자열을 모두 소문자로/대문자로 변환</li>
<li><strong>SUBSTR(s,n,k)</strong>: 대상 문자열을 지정된 자리에서부터, 지정된 길이만큼 잘라서 반환</li>
<li><strong>TRIM(c FROM s)</strong>: 대상 문자열의 양쪽에서 지정된 문자를 삭제. (문자열만 넣을 시 기본으로 공백 제거)</li>
</ul>
<blockquote>
<h4 id="숫자값-반환-함수">숫자값 반환 함수</h4>
</blockquote>
<ul>
<li><strong>LENGTH(s)</strong>: 대상 문자열의 바이트를 반환 (알파벳은 1바이트, 한글은 3바이트)</li>
<li><strong>CHAR_LENGTH(s)</strong>: 문자열의 문자 수를 반환</li>
</ul>
<h4 id="replace-함수">REPLACE 함수</h4>
<ul>
<li>문자열을 치환하는 함수<pre><code class="language-sql">SELECT bookid, REPLACE(bookname, &#39;야구&#39;, &#39;농구&#39;) bookname, publisher, price
FROM book;</code></pre>
<img src="https://velog.velcdn.com/images/summeryoung_/post/f0860463-da75-4628-9d3f-9376c3c0c301/image.png" alt=""></li>
</ul>
<h4 id="length-char_length-함수">LENGTH, CHAR_LENGTH 함수</h4>
<ul>
<li><code>LENGTH()</code>: 바이트 수를 가져오는 함수</li>
<li><code>CHAR_LENGTH()</code>: 문자의 수를 가져오는 함수</li>
</ul>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/25395853-9428-4f7e-b317-604f7dc6eb05/image.png" alt=""></p>
<h4 id="이름-가리기masking-실습">이름 가리기(masking) 실습</h4>
<pre><code class="language-sql">SELECT CONCAT(LEFT(name,1), &#39;*&#39;,RIGHT(name,1)) AS marked_name
FROM customer;</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/304f4660-cbd3-472e-b5ac-6021e6c33877/image.png" alt=""></p>
<pre><code class="language-java">SELECT
    CASE
        WHEN CHAR_LENGTH(name) = 2
            THEN CONCAT(LEFT(name,1),&#39;*&#39;)
        WHEN CHAR_LENGTH(name) &gt; 2
            THEN CONCAT(LEFT(name,1), REPEAT(&#39;*&#39;,CHAR_LENGTH(name)-2), RIGHT(name,1))
        ELSE name
    END
FROM customer;</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/0c58796f-6269-46c9-a45d-38245370781a/image.png" alt=""></p>
<hr>
<h3 id="3-날짜시간-함수">(3) 날짜/시간 함수</h3>
<ul>
<li>날짜와 시간 부분을 나타내는 <strong><code>format</code></strong>으로 표기</li>
<li><code>format</code>은 날짜 형식 지정자로 날짜와 시간 부분을 표기하기 위해 특별한 규칙을 가짐.</li>
</ul>
<blockquote>
</blockquote>
<ul>
<li><code>STR_TO_DATE(string format)</code>: 문자열 데이터를 날짜형으로 반환</li>
<li><code>DATE_FORMAT(date format)</code>: 날짜형 데이터를 문자열로 반환</li>
<li><code>ADDDATE(date interval)</code>: DATE형의 날짜에서 INTERVAL 지정한 시간만큼 더함</li>
<li><code>DATE(date)</code>: DATE형의 날짜 부분을 반환</li>
<li><code>DATEDIFF(date1, date2)</code>: DATE형의 date1-date2 날짜 차이를 반환함</li>
<li><code>SYSDATE</code>: DBMS 상의 오늘 날짜를 반환.</li>
</ul>
<ul>
<li>DBMS마다 함수 이름과 동작이 다름</li>
<li>날짜형 데이터는 &#39;-&#39;와 &#39;+&#39;를 사용하여 원하는 날짜로부터 이전(-)과 이후(+)를 계산할 수 있음</li>
</ul>
<blockquote>
<p><strong>📌 날짜, 시간함수 사용 주의점</strong><br></p>
</blockquote>
<ol>
<li>DBMS마다 이름과 동작, 의미가 다름</li>
<li>타임존 문제</li>
</ol>
<ul>
<li><code>NOW()</code>나 <code>CURRENT_TIMESTAMP</code>는 DB 서버의 타임존을 기준으로 반환.</li>
<li>서버와 사용자가 다른 지역에 있으면 시간이 어긋날 수 있으므로, 필요시 따로 맞춰줘야함.</li>
</ul>
<ol start="3">
<li>날짜 포맷 출력: <strong><code>DATE_FORMAT()</code></strong>(MySQL) <strong><code>TO_CHAR()</code></strong>(Oracle/Postgres) 포맷 지정</li>
<li><strong>NULL 처리</strong>: </li>
</ol>
<ul>
<li>날짜 컬럼이 NULL이면 함수 적용 시 에러가 발생</li>
<li>기본값으로 설정해둬야함: <code>IFNULL()</code>, <code>COALESCE()</code> 사용해서 널 처리.</li>
</ul>
<ol start="5">
<li>성능 고려: select에서 조회용으로 사용할 때</li>
</ol>
<ul>
<li>다른 부분에서 속성을 함수를 사용해 변환하면 <strong>인덱스에 저장된 값이 아니라 함수 결과를 새로 계산</strong>하여 <strong>테이블 풀 스캔</strong>을 할 가능성이 높음</li>
<li>테이블 풀스캔의 경우 성능이 떨어짐</li>
<li><code>WHEN DATE(order_date) = &#39;2026-04-02&#39;</code> 보다 <code>WHEN order_date &gt;= &#39;2026-03-24 00:00:00&#39; AND order_date &lt; &#39;2026-03-39 00:00:00&#39;</code>가 좋음.</li>
</ul>
<span style="font-size:15px; color:gray">
+ [ MariaDB에서 `NOW()`와 `SYSDATE()`] <br>
NOW(): 쿼리 실행 시작 시점의 시간을 반환. 같은 쿼리 내에서는 항상 동일한 값으로 유지
SYSDATE(): 함수 호출 순간의 시스템 시간을 반환. 같은 쿼리 내에서도 호출 시점마다 값이 달라짐.
</span>

<h4 id="1-adddatedate-interval">(1) ADDDATE(date, interval)</h4>
<p><code>ADDDATE(&#39;날짜&#39;, &#39;INTERVAL 수치단위&#39;)</code></p>
<ul>
<li>지정한 날짜에 <strong>일(day) 또는 시간(interval)</strong>을 더해 새로운 날짜를 반환하는 함수</li>
</ul>
<pre><code class="language-sql">SET @value = &#39;2024-04-01&#39;;

SELECT ADDDATE(@value, INTERVAL -10 DAY) &quot;BEFORE&quot;, ADDDATE(@value, INTERVAL 10 DAY) &quot;AFTER&quot;;</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/0f73b1c9-e746-4d47-ac48-18ae84a0918a/image.png" alt=""></p>
<h4 id="2-format의-주요-지정자">(2) format의 주요 지정자</h4>
<p><strong>1. 요일</strong></p>
<ul>
<li><code>%w</code>: 요일 순서 (0~6, Sunday=0)</li>
<li><code>%W</code>: 요일 (Sunday~Saturday)</li>
<li><code>%a</code>: 요일의 약자(Sun~Sat)<br>

</li>
</ul>
<p><strong>2. 날짜</strong></p>
<ul>
<li><code>%d</code>: 한 달 중 날짜 (00~31)</li>
<li><code>%j</code>: 1년 중 날짜 (001~366)<br>

</li>
</ul>
<p><strong>3. 시간</strong></p>
<ul>
<li><code>%h</code>: 12시간 (0~12)</li>
<li><code>%H</code>: 24시간 (0~24)</li>
<li><code>%i</code>: 분 (0~59)</li>
<li><code>%s</code>: 초(0~59)<br>

</li>
</ul>
<p><strong>4. 월</strong></p>
<ul>
<li><code>%m</code>: 월 순서 (01~12, January = 01)</li>
<li><code>%M</code>: 월 이름 (January ~ December)</li>
<li><code>%b</code>: 월 이름 약어(Jan~Dec)</li>
</ul>
<br>

<p><strong>5. 연도</strong></p>
<ul>
<li><code>%Y</code>: 4자리 연도</li>
<li><code>%y</code>: 4자리 연도의 마지막 2자리</li>
</ul>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/f87ee528-21cf-489e-ac88-673aa1e9b648/image.png" alt=""></p>
<h4 id="3-str_to_date-함수-date_format-함수">(3) STR_TO_DATE 함수, DATE_FORMAT 함수</h4>
<ul>
<li><code>STR_TO_DATE</code>: CHAR 형(문자열)으로 저장된 날짜를 DATE형으로 변환</li>
<li><code>DATE_FORMAT</code>: 날짜형을 문자형으로 변환함</li>
</ul>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/2ed3003e-8d13-490a-bf59-6face4998514/image.png" alt=""></p>
<h4 id="4-sysdate-함수">(4) SYSDATE 함수</h4>
<ul>
<li>데이터베이스에 설정된 현재 날짜와 시간을 반환하는 함수</li>
</ul>
<hr>
<h2 id="1-2-null-값-처리">1-2. NULL 값 처리</h2>
<h3 id="널null-값">널(NULL) 값</h3>
<ul>
<li><p>아직 <strong>지정되지 않은 값</strong>, 즉 값을 알 수도 없고 적용할 수도 없음</p>
</li>
<li><p>&#39;0&#39;이나 빈 문자  또는 공백과는 다른 특별한 값으로 **비교연산자로 비교할 수도 없고, 연산 수행의 결과도 NULL로 반환됨.</p>
</li>
<li><p>NULL 값에 대한 연산 및 집계함수:</p>
<ul>
<li><strong>NULL + 숫자</strong>의 연산 결과는 <strong>NULL</strong></li>
<li>집계 함수를 사용할 때에 <strong>NULL이 포함된 행은 집계에서 제외</strong> (해당 행이 하나도 없을 경우, SUM, AVG의 결과는 NULL, COUNT 함수의 결과는 0이됨)</li>
</ul>
</li>
</ul>
<br>

<h3 id="ifnull-함수">IFNULL 함수</h3>
<ul>
<li><p>NULL 값을 다른 값으로 대치하여 연산하거나 다른 값으로 출력</p>
</li>
<li><p><code>IFNULL(속성, 값)</code>의 형태로 사용하며 속성값이 널일때 &#39;값&#39;으로 대치한다.</p>
</li>
<li><p>MySQL, MariaDB에서 사용</p>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/6d11f5b0-dcde-4b14-a4cc-9a2969c9209a/image.png" alt=""></p>
</li>
</ul>
<h3 id="coalesce-함수">COALESCE 함수</h3>
<ul>
<li>표준 SQL에서 지원</li>
<li><code>COALESCE(인자1, 인자2,...)</code>: 여러 개의 인자 중 널값이 아닌 첫 번째 값을 반환하며 <code>IFNULL</code>과 마찬가지로 <code>COALESCE(속성, &#39;값&#39;)</code>으로 작성하면 널 값을 &#39;값&#39;으로 채워서 출력할 수 있음.</li>
</ul>
<hr>
<h2 id="1-3-행번호-출력">1-3. 행번호 출력</h2>
<h3 id="set">SET</h3>
<ul>
<li>MySQL에서는 <strong>변수</strong>는 이름 앞에 <strong><code>@</code></strong> 기호를 붙이며, <strong>치환문에는 SET과 <code>:=</code> 기호를 사용</strong>한다.</li>
</ul>
<pre><code class="language-sql">SET @seq:=0;

SELECT (@seq := @seq+1) &#39;순번&#39;, custid, name, phone
FROM customer
WHERE @seq &lt; 2;</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/72944518-6a7a-456d-b292-c23a3ae63b00/image.png" alt=""></p>
<p>위처럼 적었을 때는 결과가 생각한 것처럼 잘 나오는데, 좀 다르게 <code>@seq &lt;2</code>인 경우에만 출력하려고 하면, 예상과는 다르게 아래처럼 나온다.</p>
<pre><code class="language-sql">SET @seq:=0;

SELECT (@seq := @seq+1) &#39;순번&#39;, custid, name, phone
FROM customer
WHERE @seq &gt; 2;</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/1a928ae9-7720-43d1-a40f-38659d5e7e5b/image.png" alt=""></p>
<p>이유는 쿼리 실행 시 동작 순서와 관련이 있다.</p>
<p><code>@seq</code>를 증가시켜주는 부분이 SELECT에 있어서 인데, WHERE절에서 조건을 체크하고 그 행이 조건에 맞으면 이제 SELECT로 가게되는데, 위의 경우에는 처음부터 <code>@seq</code>가 조건에 맞지 않아 SELECT절로 이동하지 않으면서 <code>@seq</code>가 그 뒤의 행들에서도 전혀 증가되지 않고 계속 0으로 남아있게 된다.</p>
<p>그래서 만약에 위에처럼 쿼리를 작성하고 싶으면 아래처럼 <code>@seq</code> 증가시켜주는 부분을 WHERE 절에서 하는 하도록 작성해주면 되었다.</p>
<p>이게 쿼리 실행했을 때 <strong>각 행 별로</strong> 이제 쿼리의 내용대로 돌아가면서? 실행? 조건 체크? 등이 이루어지는 식이고 그 행 별로 이제 적용이 되는? 느낌이었다.</p>
<pre><code class="language-sql">SET @seq:=0;

SELECT (@seq) &#39;순번&#39;, custid, name, phone
FROM customer
WHERE (@seq := @seq+1) &gt; 2;</code></pre>
<hr>
<h2 id="1-4-case-when">1-4 CASE WHEN</h2>
<ul>
<li><strong>조건에 따라 다른 값을 반환</strong></li>
<li>IF-THNE-ELSE와 비슷하게 동작하며 <strong>집계함수와 함께 쓰거나 출력 컬럼을 가공</strong>할 때 유용하다</li>
</ul>
<pre><code class="language-sql">CASE
    WHEN 조건1 THEN 결과1 #WHEN: 조건 지정
    WHEN 조건2 THEN 결과2 #THEN: 조건이 참이면 반환할 값
    ...
    ELSE 기본값 #ELSE: 모든 조건이 거짓일 때 반환할 기본값
END AS 별칭 #END: CASE문 종료</code></pre>
<p>예) 국내 거주자/국외 거주자 출력</p>
<pre><code class="language-sql">SELECT custid, name,
    CASE
        WHEN address LIKE &#39;%대한민국%&#39;
        THEN &quot;국내&quot;
        ELSE &quot;국외&quot;
    END AS &quot;국적&quot;, phone
FROM customer;</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/ba26ec45-a864-4409-84f4-3fd4801db554/image.png" alt=""></p>
<hr>
<h1 id="2-부속질의">2. 부속질의</h1>
<h2 id="2-1-부속질의-서브쿼리">2-1. 부속질의 (=서브쿼리)</h2>
<ul>
<li>하나의 SQL문 안에 <strong>다른 SQL문이 중첩</strong>된 질의</li>
<li>주로 메인 쿼리의 조건에 따라 서브 쿼리의 결과를 가져와서 메인 쿼리에서 사용하는 용도로 활용</li>
<li>다른 테이블에서 가져온 데이터로 현재 테이블의 정보를 찾거나 가공하는 등의 작업 수행 가능</li>
<li>조인을 사용하는 방법도 있지만 서브 쿼리가 더 유리한 경우에 서브 쿼리를 사용</li>
</ul>
<blockquote>
<p><strong>부속 질의가 유리할 때</strong></p>
</blockquote>
<ul>
<li><strong>필터링 대상이 매우 적을 때</strong>: 서브 쿼리의 결과값이 단 하나임이 보장되고, 메인 테이블의 양이 방대할 때</li>
<li><strong>복잡한 집계가 포함될 때</strong>: 조인으로 풀면 중복 데이터가 너무 많이 발생해 계산이 꼬이는 경우.<br></li>
<li><code>EXPLAIN</code>을 사용하면 쿼리가 훑고간 행의 수, 실제 남은 데이터의 비율, 인덱스를 사용하는(using index) 아니면 풀테이블 스캔(using filesort)를 하는지 등을 확인해 볼 수 있음.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/25bf3c9d-7b50-4ff7-b7d1-083037d1c9bc/image.png" alt=""></p>
<h4 id="부속질의의-종류">부속질의의 종류</h4>
<table>
<thead>
<tr>
<th align="center">부속질의</th>
<th align="center">실무 용어</th>
<th align="center">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="center">WHERE 부속질의</td>
<td align="center"><strong>중첩질의</strong></td>
<td align="center">WHERE 절에서 술어와 같이 사용되며 결과를 한정. <br> <strong>상관 또는 비상관</strong> 형태</td>
</tr>
<tr>
<td align="center">SELECT 부속질의</td>
<td align="center"><strong>스칼라 부속질의</strong></td>
<td align="center">SELECT 절에서 사용되며 <strong>단일값</strong>을 반환</td>
</tr>
<tr>
<td align="center">FROM 부속질의</td>
<td align="center"><strong>인라인 뷰</strong></td>
<td align="center">FROM 절에서 결과를 뷰 형태로 반환</td>
</tr>
</tbody></table>
<br>

<h3 id="where-부속질의">WHERE 부속질의</h3>
<ul>
<li>중첩질의. 보통 데이터를 <strong>선택 하는 조건 혹은 술어</strong>와 같이 사용.</li>
<li>비교/집합/한정/존재 연산자와 사용.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/ad06486d-49ae-4659-9495-37a1d7abf0dd/image.png" alt=""></p>
<h4 id="비교연산자">비교연산자</h4>
<ul>
<li>비교 연산자 사용 시 <strong>부속질의가 반드시 단일 행, 단일 열을 반환</strong>해야하며, 아닐 경우에는 질의를 처리할 수 없다.</li>
<li>주질의 대상 열 값과 결과 값을 비교 연산자에 적용하여, 참인 경우에만 주질의의 해당 열을 출력한다.</li>
</ul>
<h4 id="in-not-in-집합-연산자">IN, NOT IN (집합 연산자)</h4>
<ul>
<li>IN/NOT IN 연산자는 <strong>주질의의 속성값이 부속질의에서 제공한 결과 집합에 있는지/없는지 확인</strong>하는 역할</li>
<li>주질의는 WHERE절에 사용되는 속성값을 부속질의의 결과 집합과 비교해 하나라도 있으면 참이 됨</li>
</ul>
<h4 id="all-someany한정-연산자">ALL, SOME/ANY(한정 연산자)</h4>
<ul>
<li>하나의 결과값이 아니라 여러 결과값과 비교할 수 있게 해줌</li>
<li>ALL: 모든 결과값보다~ (크거나 작다 등 비교연산자 사용)</li>
<li>SOME/ANY: 반환한 결과값 중 하나라도 조건 만족</li>
</ul>
<pre><code class="language-sql">#예시
SELECT 이름 FROM 학생 
WHERE 키 &gt; ALL (SELECT 키 FROM 학생 WHERE 학년 = 3);

SELECT 이름 FROM 학생 
WHERE 키 &gt; ANY (SELECT 키 FROM 학생 WHERE 학년 = 3);</code></pre>
<p>예) </p>
<pre><code class="language-sql">#동작하지 않는 경우
SELECT *
FROM customer
WHERE custid = (SELECT custid
                FROM orders);

 #수정
 SELECT *
FROM customer
WHERE custid IN (SELECT custid
                FROM orders);</code></pre>
<blockquote>
<p><strong>부속질의 vs 상관질의</strong></p>
</blockquote>
<ul>
<li>부속질의: 단순히 쿼리 안에 들어간 SELECT문으로 독립적으로 실행할 수 있음</li>
<li>상관질의: 부속질의 중 <strong>메인 쿼리의 컬럼을 참조</strong>하여 메인 쿼리의 <strong>각 행마다 실행</strong>되는 경우.</li>
</ul>
<h4 id="exists-not-exists-존재-연산자">EXISTS, NOT EXISTS (존재 연산자)</h4>
<ul>
<li>데이터의 존재 여부를 확인<pre><code class="language-sql">WHERE [NOT] EXISTS (부속질의)</code></pre>
</li>
<li>메인 쿼리와 서브 쿼리가 상관 부속질의의 관계일 때 둘 사이의 연결 관계를 잘 설정해줘야한다</li>
</ul>
<pre><code class="language-sql">SELECT *
FROM customer C
WHERE EXISTS (SELECT custid
                     FROM orders O
                     WHERE C.custid = O.custid);</code></pre>
<pre><code>위에서는 `C.custid = O.custid`로 둘을 연결해주었다.</code></pre><p><img src="https://velog.velcdn.com/images/summeryoung_/post/1cdcba20-4191-4f31-b263-fa967e36e0ba/image.png" alt=""></p>
<hr>
<h2 id="2-2-스칼라-부속질의-select-부속질의">2-2. 스칼라 부속질의 (SELECT 부속질의)</h2>
<ul>
<li>부속질의의 결과 값을 <strong>단일 행, 단일 열의 스칼라 값</strong>으로 반환</li>
<li>만약 결과 값이 다중 행이거나 다중 열이면 DBMS는 어떤 행/열을 출력해야하는지 몰라 에러를 출력</li>
<li>결과값이 없는 경우에는 NULL 출력</li>
<li>SELECT 절에서 사용하니 고객별 주문 총횟수/총액과 같은 부분을 출력할 때 사용, 이때 하나의 행에 출력되는 값이니 여러 개일 수 없는 느낌...?</li>
</ul>
<p>예)
<strong>1. GROUP BY 없이</strong></p>
<pre><code class="language-sql">SELECT custid, (SELECT COUNT(*)
                FROM orders O
                WHERE C.custid = O.custid) AS &quot;주문 횟수&quot;
FROM customer C;</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/49b42732-f3c2-4d63-8439-2b9797b6b10a/image.png" alt=""></p>
<ul>
<li>WHERE 절에서 고객의 주문을 찾고, <strong>주문이 없는 경우 결과가 0</strong>이 되는데 이때 <code>GROUP BY</code>가 없이 <code>COUNT()</code>를 쓰기에 대상이 없더라도 무조건 0이 됨</li>
<li>결과: 주문 안 한 고객의 옆에는 0<br>

</li>
</ul>
<p><strong>2. GROUP BY 있는 상태</strong></p>
<pre><code class="language-sql">SELECT custid, (SELECT COUNT(*)
                FROM orders O
                WHERE C.custid = O.custid
                GROUP BY custid) AS &quot;주문 횟수&quot;
FROM customer C;</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/3cb7c97c-cee1-4245-8f22-2210bacbf67a/image.png" alt=""></p>
<ul>
<li>WHERE절 이후 그룹별로 묶으려고하는데, 이때 주문이 없으면 WHERE절에서 남은 데이터가 하나도 없기에 그룹 자체가 만들어지지 않음</li>
<li>그룹이 없으면 <code>COUNT()</code>의 대상도 없기에 아무 행도 반환하지 않음.</li>
</ul>
<br>

<h4 id="update-문에서의-스칼라-부속질의select-부속질의">UPDATE 문에서의 스칼라 부속질의(SELECT 부속질의)</h4>
<ul>
<li>스칼라 부속질의(SELECT 부속질의)는 UPDATE문에서도 사용할 수 있음</li>
</ul>
<pre><code class="language-sql">SET SQL_SAFE_UPDATES = 0;

UPDATE orders
SET bookname = (SELECT bookname
                FROM book
                WHERE book.bookid = orders.bookid);

SELECT *
FROM orders;</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/9dbd32bf-8a63-48a6-b3e5-72b1fec5d1c9/image.png" alt=""></p>
<hr>
<h2 id="2-3-인라인-뷰-from-부속질의">2-3. 인라인 뷰 (FROM 부속질의)</h2>
<ul>
<li>FROM 절에서 사용되는 부속질의</li>
<li><strong>뷰: 기존 테이블로부터 일시적으로 만들어지는 가상의 테이블</strong></li>
</ul>
<p>예)</p>
<pre><code class="language-sql">SELECT C.name, SUM(o.saleprice) &#39;total&#39;
FROM (SELECT custid, name
      FROM customer
      WHERE custid &lt;= 2) C,
      orders O
WHERE C.custid = O.custid
GROUP BY C.name;</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/42d5825b-50f9-4236-a8d6-3b7e50e5de12/image.png" alt=""></p>
<hr>
<h2 id="2-4-cte-common-table-expression-with">2-4. CTE (Common Table Expression, WITH)</h2>
<h3 id="with-cte명-as-select">WITH CTE명 AS (SELECT)</h3>
<ul>
<li>복잡한 SQL 쿼리 내에서 <strong>일시적인 결과 집합 (임시테이블)</strong>을 정의하여, 가독성을 높이고 쿼리를 구조화</li>
<li>메인 쿼리에서 일반 테이블처럼 <strong>재사용</strong>하거나, <strong>재귀 쿼리</strong> 구현에 활용됨</li>
<li>쿼리 실행에만 존재하며 데이터베이스에 영구적으로 저장되지 않음.</li>
</ul>
<pre><code class="language-sql">WITH CTE이름 AS (
    SELECT...
)
#,나 ; 적어주지 않고

#메인 쿼리는 이 아래
SELECT ...
FROM CTE이름;</code></pre>
<p>예)</p>
<pre><code class="language-sql">WITH sales_summary AS (
    SELECT o.custid, c.name, SUM(o.saleprice) AS total_sales
    FROM orders o
         JOIN customer c
         ON o.custid = c.custid
    GROUP BY o.custid
)

SELECT custid, name, total_sales
FROM sales_summary
ORDER BY total_sales DESC;</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/139a1efa-adf3-4c37-a417-b5239ee54209/image.png" alt=""></p>
<hr>
<h2 id="2-5-윈도우-함수">2-5. 윈도우 함수</h2>
<h3 id="sql-윈도우-함수">SQL 윈도우 함수</h3>
<p>: 테이블의 <strong>행과 행 간의 관계를 정의하여 데이터를 윈도우(틀)로 그룹화하여 사용하는 함수</strong></p>
<ul>
<li>각 행에 대한 <strong>집계나 순위 계산 결과를 추가</strong>하여 사용</li>
<li><strong>GROUP BY와 달리 행의 개수를 유지하면서 그룹 내 계산 결과를 각 행에 표시</strong></li>
<li>복잡한 조인(JOIN) 없이 행 간 계산, 순위, 누적합계 등을 구할 때 유용</li>
<li><strong><code>OVER()</code></strong> 절과 함께 사용되어 PARTITION BY와 ORDER BY로 범위와 순서 지정</li>
</ul>
<p><span style="font-size:15px"> GROUP BY랑 비슷한데 행이 없어지지 않는다... <br></p>
<p><code>GROUP BY</code>를 하는 경우에는 여러 개의 행이 그룹별로 묶여 사라지지만, 윈도우 함수를 쓰는 경우에는 행 옆에 계산 결과를 붙여주는 식.</span></p>
<blockquote>
<p>SELECT <strong>함수명() OVER (PARTITION BY 컬럼명 ORDER BY 컬럼명)</strong>
FROM 테이블명;
<br></p>
</blockquote>
<ul>
<li><strong>PARTITION BY</strong>: 계산을 수행할 그룹을 나눔</li>
<li><strong>ORDER BY</strong>: 그 그룹 안에서 계산을 수행할 순서</li>
</ul>
<h3 id="주요-윈도우-함수-유형">주요 윈도우 함수 유형</h3>
<h4 id="1-순위함수">1. 순위함수</h4>
<ul>
<li><code>ROW_NUMBER()</code>: 줄 세우기 (1,2,3,4...)</li>
<li><code>RANK()</code>: 공동 순위만큼 건너뛰기 (1,2,2,3...)</li>
<li><code>DENSE_RANK()</code>: 공동순위가 있어도 촘촘하게 (1,2,2,3,...)</li>
</ul>
<pre><code class="language-sql">SELECT B.publisher, B.bookname, 
       SUM(O.saleprice) AS total_sales, 
       DENSE_RANK() OVER (PARTITION BY B.publisher ORDER BY SUM(O.saleprice) DESC) AS rank_in_publisher
FROM orders O 
     JOIN book B 
     ON O.bookid = B.bookid
GROUP BY B.publisher, B.bookname
ORDER BY B.publisher, rank_in_publisher;</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/048e67e9-0346-4907-967c-c912595e0809/image.png" alt=""></p>
<h4 id="2-집계함수">2. 집계함수</h4>
<ul>
<li><p>누적 합계를 구할 때 편함.</p>
</li>
<li><p>순위함수(Ranking): ROW_NUMBER(), RANK(), DENSE_RANK()</p>
</li>
<li><p>집계함수(Aggregate): SUM(), AVG(), COUNT(), MAX(), MIN()</p>
</li>
<li><p>분석/값 함수(Value): LEAD(), LAG(), FIRST_VALUE(), LAST_VALUE()...</p>
</li>
</ul>
<p>예) 출판사별로 saleprice 누적합 구하기</p>
<pre><code class="language-sql">SELECT B.publisher, B.bookname,
       SUM(SUM(O.saleprice)) OVER (PARTITION BY B.publisher 
                                   ORDER BY SUM(O.saleprice), B.bookname) AS &#39;출판사별 총 판매액&#39;
FROM orders O 
     JOIN book B 
     ON O.bookid = B.bookid
GROUP BY B.publisher, B.bookname
ORDER BY B.publisher, `출판사별 총 판매액`;</code></pre>
<p>orders와 book 조인 -&gt; 출판사, 책 이름 별로 그루핑 <code>출판사-책이름-그 책의 총판매액: SUM(O.saleprice)</code> -&gt; 윈도우 함수 안에서 <code>PARTITION BY</code>를 통해 출판사별로 분리, 출판사별 <code>SUM(O.saleprice)</code> 계산. -&gt; <code>ORDER BY</code> 통해 책별 총판매액 &amp; 책이름으로 정렬 (같은 가격이어도 분리되어서 나오게) -&gt; 외부 <code>ORDER BY</code>에 윈도우 함수 결과 속성 추가해서 정렬결과대로 보기.</p>
<p><code>SUM(SUM(O.saleprice)</code></p>
<ul>
<li>안쪽 SUM: GROUP BY 결과로 나온 <strong>책 한 권의 합계</strong></li>
<li>바깥 SUM: 윈도우 함수가 만드는 <strong>출판사 내의 누적 합계</strong>
<img src="https://velog.velcdn.com/images/summeryoung_/post/1352b061-0ecc-4187-99a6-c3004895c9ae/image.png" alt=""></li>
</ul>
<h4 id="3-분석값-함수">3. 분석/값 함수</h4>
<ul>
<li>이전 행이나 다음 행의 데이터를 가져올 때</li>
<li>JOIN 없이도 어제와 오늘의 차이를 계산할 수 있음</li>
<li><code>LAG(컬럼)</code>: 현재 행보다 이전(뒤에있는) 행의 값을 가져옴 (과거)</li>
<li><code>LEAD(컬럼)</code>: 현재 행보다 다음(앞에있는) 행의 값을 가져옴 (미래)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/e6583a8d-f432-49e0-a34d-13e76bee05a8/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[자바 ORM 표준 JPA 프로그래밍] 5주차 스터디]]></title>
            <link>https://velog.io/@summeryoung_/%EC%9E%90%EB%B0%94-ORM-%ED%91%9C%EC%A4%80-JPA-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-5%EC%A3%BC%EC%B0%A8-%EC%8A%A4%ED%84%B0%EB%94%94</link>
            <guid>https://velog.io/@summeryoung_/%EC%9E%90%EB%B0%94-ORM-%ED%91%9C%EC%A4%80-JPA-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-5%EC%A3%BC%EC%B0%A8-%EC%8A%A4%ED%84%B0%EB%94%94</guid>
            <pubDate>Tue, 31 Mar 2026 06:16:55 GMT</pubDate>
            <description><![CDATA[<h1 id="6장-다양한-연관관계-매핑">6장. 다양한 연관관계 매핑</h1>
<p>엔티티의 연관관계 매핑 시에는 아래 3가지를 고려하게된다.</p>
<ul>
<li>다중성</li>
<li>단방향, 양방향</li>
<li>연관관계의 주인</li>
</ul>
<p>두 엔티티가 <strong>일대일 관계</strong>인지 <strong>일대단 관계</strong>인지 다중성을 고려하여야한다. 단방향일 때는 괜찮지만, <strong>양방향일 때는 연관관계의 주인을 정해야</strong>한다.</p>
<h4 id="다중성">다중성</h4>
<ul>
<li>다대일 (<code>@ManyToOne</code>)</li>
<li>일대다 (<code>@OneToMany</code>)</li>
<li>일대일 (<code>@OneToOne</code>)</li>
<li>다대다 (<code>@ManyToMany</code>)</li>
</ul>
<h4 id="단방향-양방향">단방향, 양방향</h4>
<p>테이블은 외래 키 하나로 조인을 사용해 양방향으로 쿼리가 가능하기에 방향이라는 개념이 없다. 다만, <strong>객체</strong>는 <strong>참조 필드를 가진 객체만 연관 객체를 조회</strong>할 수 있다.</p>
<ul>
<li>단방향: 객체 관계에서 한 쪽만 참조하는 것을 말함</li>
<li>양방향: 객체 양쪽이 서로를 참조하는 것을 말함</li>
</ul>
<h4 id="연관관계의-주인">연관관계의 주인</h4>
<p>데이터베이스에서는 외래 키 하나로 두 테이블이 연관관계를 맺는다. 이때 <strong>외래 키는 하나</strong>이다. 데이터베이스에 맞춰 객체를 양방향으로 매핑하는 경우에는 다만, 이 외래 키를 관리하는 곳이 <strong>2곳</strong>이 되어버린다.</p>
<p>이 관리를 위해 <strong>연관관계의 주인</strong>을 설정해야한다. 대부분은 <strong>외래 키를 가지고 있는 테이블의 엔티티</strong>가 관계의 주인이된다.</p>
<hr>
<h2 id="61-다대일">6.1 다대일</h2>
<p>다대일의 관계의 반대는 항상 일대다이다. 데이터베이스에서 테이블의 일(1),  다(N) 관계에서 <strong>외래 키는 항상 다(N)</strong>쪽에 있기에 다쪽이 <strong>연관관계의 주인</strong>이다.</p>
<h3 id="다대일-단방향-n1">다대일 단방향 [N:1]</h3>
<h4 id="member">Member</h4>
<pre><code class="language-java">@Entity
public class Member {
    @Id
    @Column(name = &quot;MEMBER_ID&quot;)
    private String id;

    private String username;

    @ManyToOne
    @JoinColumn(name = &quot;TEAM_ID&quot;)
    private Team team;
}</code></pre>
<h4 id="team">Team</h4>
<pre><code class="language-java">@Entity
public class Team {
    @Id @GeneratedValue
    @Column(name = &quot;TEAM_ID&quot;)
    private Long id;

    private String name;
}</code></pre>
<p>회원은 <code>Member.team</code>으로 팀 엔티티를 참조할 수 있지만, 그 반대로 팀에서는 회원을 참조하는 필드가 없는 위의 상황을 <strong>다대일 단방향 관계</strong>라고 한다.</p>
<p><code>@ManyToOne</code> 어노테이션과 함께 <code>@JoinColumn (name = &quot;TEAM_ID&quot;)</code>를 사용해 <code>Member.team</code> 필드를 <code>TEAM_ID</code> 외래 키와 매핑한다. 그렇기에 회원의 필드로 회원 테이블의 외래키를 관리하게된다.</p>
<h3 id="다대일-양방향-n1">다대일 양방향 [N:1]</h3>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/abfeee0e-1040-43a3-9bfd-5153558c7039/image.png" alt=""></p>
<p>다대일 양방향의 객체 연관관계에서 <strong>실선이 연관관계의 주인</strong>이고, <strong>점선은 연관관계의 주인이 아니다</strong>.</p>
<ul>
<li><p>양방향은 <strong>외래 키가 있는 쪽이 연관관계의 주인</strong>
일대다와 다대일 연관관계에서는 항상 <strong>다(N)에 외래키가 존재</strong>. JPA는 외래 키를 관리할 때 연관관계의 주인만 사용하며, 주인이 아닌 엔티티의 필드는 조회를 위한 JPQL 또는 객체 그래프 탐색을 위해서 사용.</p>
</li>
<li><p>양방향 연관관계는 <strong>항상 서로를 참조</strong>
양방향 연관관계는 항상 서로를 참조해야하는데, 한 쪽만 참조하는 경우에는 이 연관관계가 성립하지 않는다. <strong>항상 서로를 참조하게 하기 위해서는 연관관계 편의 메소드를 작성</strong>하는 것이 좋다. <br><br><code>setTeam()</code> 또는 <code>addMembers()</code> 같은 메소드가 이런 편의 메소드이다. 편의 메소드는 양쪽에 다 작성하는 경우 무한루프에 빠질 수 있기에 주의해야한다.</p>
</li>
</ul>
<hr>
<h2 id="62-일대다">6.2 일대다</h2>
<p>일대다 관계는 다대일 관계의 반대 방향이다. 다만, <strong>일대다 관계는 엔티티를 하나 이상 참조할 수 있기에 자바 컬렉션 중 하나를 사용</strong>해야한다.</p>
<h3 id="일대다-단방향-1n">일대다 단방향 [1:N]</h3>
<p>하나의 팀이 여러 회원을 참조할 수 있는 이런 관계를 일대다 관계라한다. 만약 팀은 회원들은 참조하지만, 회원이 팀을 참조하지 않으면 이를 두고 일대다 단방향이라한다.
<img src="https://velog.velcdn.com/images/summeryoung_/post/daf0b6ba-3829-4465-a8d9-b2918fe28656/image.png" alt=""></p>
<p>일대다 단방향의 경우에는 팀 엔티티의 <code>Team.members</code>를 통해 회원 테이블의 <code>TEAM_ID</code> 외래 키를 관리하게 된다. 보통은 자신이 매핑한 테이블의 외래 키를 관리하는데 여기서는 반대쪽 테이블의 외래 키를 관리하게 된다. 이는 특이한 양상이다.</p>
<pre><code class="language-java">@Entity
public clas Team{
    @Id @GeneratedValue
    @Column(name = &quot;TEAM_ID&quot;)
    private Long id;

    private String name;

    @OneToMany
    @JoinColumn(name = &quot;TEAM_ID&quot;) //MEMBER 테이블의 TEAM_ID (FK)
    private List&lt;Member&gt; members = new ArrayList&lt;&gt;();
}</code></pre>
<p><strong>일대다 단방향 관계의 매핑을 위해서는 <code>@JoinColumn</code>을 명시</strong>해야한다. 그렇지 않으면 JPA는 연결 테이블을 중간에 두고, <strong>연관관계를 관리하는 조인 테이블 전략</strong>을 기본으로 사용해 매핑한다.</p>
<h4 id="일대다-단방향의-단점">일대다 단방향의 단점:</h4>
<p>매핑한 객체가 관리하는 외래 키가 다른 테이블에 있다는 점</p>
<p>자신이 관리하는 테이블에 외래 키가 있으면, 엔티티의 저장과 연관관계 처리를 INSERT SQL 한 번으로 처리할 수 있는 것과 다르게 다른 테이블에 있다면, UPDATE SQL을 추가로 실행해야함.</p>
<p>예를 들면 <code>Member</code> 엔티티는 <code>Team</code> 엔티티를 모르고, 연관관계 정보는 <code>Team</code> 에서 관리하기에 <code>Member</code> 를 저장할 때에는 <code>TEAM_ID</code>에 아무 값도 저장되지 않고 <code>Team</code> 엔티티를 저장할 때, 참조값을 확인해 회원 테이블의 <code>TEAM_ID</code> 외래 키를 업데이트한다.</p>
<p>위와 같은 단점이 있기에 <strong>일대일 단방향</strong> 매핑보다는 <strong>다대일 양방향 매핑</strong>을 사용하는 것이 좋다.</p>
<h3 id="일대다-양방향-1n-n1">일대다 양방향 [1:N, N:1]</h3>
<p>일대다 양방향 매핑은 존재하지 않기에 다대일 양방향 매핑을 사용해야한다.  정확하게는 <code>@OneToMany</code>는 양방향 매핑에서 연관관계의 주인일 수 없다.</p>
<p><code>@ManyToOne</code> - <code>@OneToMany</code> 둘 중에 연관관계의 주인은 항상 다 쪽인 <code>@ManyToOne</code>을 사용하는 곳이다. 따라서 <code>mappedBy</code> 속성은 <code>@ManyToOne</code>에만 존재한다.</p>
<p>일대다 양방향 매핑이 완전히 불가능한 것은 아니고, 일대다 단방향 매핑 반대편에 같은 외래 키를 사용하는 다대일 단방향 매핑을 읽기 전용으로 추가하면 된다.</p>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/bec628d1-86e4-451e-870c-dcc5e50e3418/image.png" alt=""></p>
<pre><code class="language-java">@Entity
public class Team {
    @Id @GeneratedValue
    @Column(name = &quot;TEAM_ID&quot;)
    private Long id;

    private String name;

    @OneToMany
    @JoinColumn (name = &quot;TEAM_ID&quot;)
    private List&lt;Member&gt; members = new ArrayList&lt;Member&gt;();
}</code></pre>
<pre><code class="language-java">@Entity
public class Member {
    @Id @GeneratedValue
    @Column (name = &quot;MEMBER_ID&quot;)
    private Long id;

    private String username;

    @ManyToOne
    @JoinColumn (name = &quot;TEAM_ID&quot;, insertable = false,
        updatable = false)
    private Team team;
}</code></pre>
<p>일대다 단방향 매핑의 반대편에 다대일 단방향 매핑을 (멤버쪽에) 추가한다. 이때 일대다 단방향 매핑과 같이 외래 키 컬럼을 매핑하는 대신 둘 다 같은 키를 관리하는 문제를 방지하고자 다대일 쪽에는 <code>insertable=false</code>와 <code>updatable = false</code>로 설정에 <strong>읽기만 가능</strong>하게 설정한다.</p>
<p>하지만 이 경우 일대다 단방향 매핑이 가지는 단점을 그대로 가지기에 <strong>다대일 양방향 매핑을 사용</strong>하는게 좋다.</p>
<hr>
<h2 id="63-일대일-11">6.3 일대일 [1:1]</h2>
<p>일대일 관계는 양쪽이 서로 하나의 관계만 가짐. </p>
<ul>
<li>일대일 관계는 그 반대도 일대일 관계</li>
<li>테이블 관계에서 일대다, 다대일은 항상 다쪽이 외래키를 가지지만, <strong>일대일 관계는 주 테이블이나 대상 테이블 둘 중 어느 곳이나 외래키를 가질 수 있음</strong></li>
</ul>
<h3 id="주-테이블에-외래-키">주 테이블에 외래 키</h3>
<p>객체지향 개발자들이 주로 해당 방식을 선호하며, JPA에서 역시 주 테이블에 외래 키가 있으면 좀 더 편리하게 매핑할 수 있음.</p>
<h4 id="단방향">단방향</h4>
<p>회원과 사물함을 예로 뒀을 때.
<img src="https://velog.velcdn.com/images/summeryoung_/post/d5fbbfb9-872d-4b66-967e-49e08ef2fd28/image.png" alt=""></p>
<h4 id="member-1">Member</h4>
<pre><code class="language-java">@Entity
public class Member {
    @Id @GeneratedValue
    @Column (name = &quot;MEMBER_ID&quot;)
    private Long id;

    private String username;

    @OneToOne
    @JoinColumn(name = &quot;LOCKER_ID&quot;)
    private Locker locker;
}</code></pre>
<h4 id="locker">Locker</h4>
<pre><code class="language-java">@Entity
public class Locker {
    @Id @GeneratedValue
    @Column (name = &quot;LOCKER_ID&quot;)
    private Long id;

    private String name;
}</code></pre>
<ul>
<li><code>@OneToOne</code>: 일대일 관계이기에 객체 매핑에 해당 어노테이션 사용. </li>
<li><code>LOCKER_ID</code>: 외래 키에 유니크 제약 조건(UNI) 추가</li>
<li>해당 관계는 다대일 단방향과 거의 비슷.</li>
</ul>
<h4 id="양방향">양방향</h4>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/f26eb2fc-f70b-42fa-b3fe-b8b5df4329f1/image.png" alt=""></p>
<p>단방향일 때와 나머지는 동일하고, 매핑이 없던 <code>Locker</code> 엔티티 내에 매핑을 해주고, 양방향 관계일 때는 관계의 주인을 설정해줘야하기에 <code>Locker</code> 엔티티에 <code>mappedBy</code>를 설정해줘, 주인이 아님을 명시한다.</p>
<pre><code class="language-java">@Entity
public class Locker {
    ...

    @OneToOne (mappedBy = &quot;locker&quot;)
    private Member member;
}</code></pre>
<h3 id="대상-테이블에-외래-키">대상 테이블에 외래 키</h3>
<h4 id="단방향-1">단방향</h4>
<p>JPA에서는 아래처럼 일대일 관계 중 대상 테이블에 외래 키가 있는 단방향 관계는 지원하지 않는다. 단방향 관계를 Locker -&gt; Member 방향으로 수정하거나, 양방향 관계로 만들어 Locker를 연관관계의 주인으로 설정해야한다.</p>
<p>JPA 2.0부터는 일대다 단방향 관계에서 대상 테이블에 외래 키가 있는 매핑을 허용했지만, 일대일 단방향은 허용되지 않는다.
<img src="https://velog.velcdn.com/images/summeryoung_/post/fcd913cb-85e5-43cc-85d4-7ac9587cd28f/image.png" alt=""></p>
<h4 id="양방향-1">양방향</h4>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/6095ca8b-fd08-4b88-8f76-9d81bf17aae2/image.png" alt=""></p>
<h4 id="member-2">Member</h4>
<pre><code class="language-java">@Entity
public class Member {
    @Id @GeneratedValue
    @Column (name = &quot;MEMEBER_ID&quot;)
    private Long id;

    private String username;

    @OneToOne (mappedBy = &quot;member&quot;)
    private Locker locker;
}</code></pre>
<h4 id="locker-1">Locker</h4>
<pre><code class="language-java">public class Locker {
    @Id @GeneratedValue
    @Column (name = &quot;LOCKER_ID&quot;)
    private Long id;

    private String name;

    @OneToOne
    @JoinColumn (name = &quot;MEMBER_ID&quot;)
    private Member member;
}</code></pre>
<p>일대일 매핑에서 대상 테이블에 외래 키를 두고 싶을 때는 이렇게 <strong>양방향</strong>으로 매핑해야한다. 주 엔티티인 <code>Member</code> 엔티티 대신, 대상 엔티티인 <code>Locker</code> 를 연관관계의 주인으로 만들어 <code>LOCKER</code> 테이블의 외래 키를 관리하도록 한다.</p>
<hr>
<h2 id="64-다대다-nn">6.4 다대다 [N:N]</h2>
<p>관계형 데이터베이스에서 <strong>정규화된 테이블 2개로 다대다 관계를 표현할 수는 없다</strong>. 따라서 중간에 <strong>연결 테이블</strong>을 추가해줘야한다.</p>
<blockquote>
<ul>
<li>회원과 상품 사이의 관계</li>
</ul>
</blockquote>
<ul>
<li>둘은 한 회원이 여러 상품과 관계가 있을 수 있음</li>
<li>한 상품이 여러 회원과 관계가 있을 수 있음</li>
</ul>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/b9e3c063-5e7b-419a-9e88-83c7627034b5/image.png" alt=""></p>
<p>하지만 또 객체는 테이블과 다르게 객체 2개로 <strong>다대다 관계</strong>를 만들 수 있다. 예로 회원 객체는 컬렉션을 사용해 상품들을 참조하고, 반대로 상품들도 컬렉션ㅇ르 사용해 회원들을 참조하면된다.</p>
<h3 id="다대다-단방향">다대다: 단방향</h3>
<h4 id="member-3">Member</h4>
<pre><code class="language-java">@Entity
public class Member {
    @Id @Column (name = &quot;MEMBER_ID&quot;)
    private String id;

    private String username;

    @ManyToMany
    @JoinTable (name = &quot;MEMBER_PRODUCT&quot;,
        joinColumns = @JoinColumn(name = &quot;MEMBER_ID&quot;),
        inverseJoinColumns = @JoinColumn (name = &quot;PRODUCT_ID&quot;))
    private List&lt;Product&gt; products = new ArrayList&lt;Product&gt;();
}</code></pre>
<h4 id="product">Product</h4>
<pre><code class="language-java">@Entity
public class Product {
    @Id @Colun (name = &quot;PRODUCT_ID&quot;)
    private String id;

    private String name;
}</code></pre>
<p><strong><code>@ManyToMany</code></strong>: 회원 엔티티와 상품 엔티티를 매핑.
<strong><code>@JoinTable</code></strong>: <code>@ManyToMany</code>와 함께 사용해서 <strong>연결 테이블을 바로 매핑</strong>하여, 회원과 상품을 연결하는 회원_상품 엔티티 없이 매핑.</p>
<blockquote>
<h4 id="jointable의-속성"><code>@JoinTable</code>의 속성</h4>
</blockquote>
<ul>
<li><code>@JoinTable.name</code>: 연결 테이블을 지정.</li>
<li><code>@JoinTable.joinColumns</code>: 현재 <strong>방향</strong>인 회원과 <strong>매핑할 조인 컬럼 정보</strong> 지정. 여기서는 MEMBER_ID</li>
<li><code>JoinTable.inverseJoinColumns</code>: <strong>반대 방향</strong>인 상품과 <strong>매핑할 조인 컬럼 정보</strong> 지정. 여기서는 PRODUCT_ID.</li>
</ul>
<h4 id="저장">저장</h4>
<pre><code class="language-java">public void save() {
    Product productA = new Product();
    productA.setId(&quot;productA&quot;);
    productA.setName(&quot;상품A&quot;);
    em.persist(productA); //영속 상태 등록

    Member member1 = new Member();
    member1.setId(&quot;member1&quot;);
    member1.setUsername(&quot;회원1&quot;);
    member1.getProducts().add(productA); //연관관계 설정
    em.persist(member1); //영속 상태 등록
}</code></pre>
<p>회원과 상품의 연관관계를 설정했기에 회원1을 저장하면 <strong>연결 테이블에도 값이 저장</strong>됨.</p>
<h4 id="조회탐색">조회/탐색</h4>
<pre><code class="language-java">public void find() {
    Member member = em.find(Member.class, &quot;member1&quot;);
    List&lt;Product&gt; products = member.getProducts(); //객체 그래프 탐색
    for (Product product : products) {
        System.out.println(&quot;product.name =&quot; + product.getName());
    }
}</code></pre>
<p>SQL에서는 알아서 조인을 사용해서 연관된 상품들을 조회를 해준다. <code>@ManyToMany</code>를 사용해 매핑해주면 JPA에서 알아서 SQL을 적합하게 만들어낸다.</p>
<h3 id="다대다-양방향">다대다: 양방향</h3>
<p>다대다 매핑이기에 역방향도 <code>@ManyToMany</code>를 사용하고, 연관관계의 주인을 지정하기 위해 <code>mappedBy</code>를 사용해주면된다.</p>
<pre><code class="language-java">@Entity
public class Product {
    @Id
    private String id;

    @ManyToMany (mappedBy = &quot;products&quot;) //역방향 추가
    private List&lt;Member&gt; members;
}</code></pre>
<p>다대다 양방향 연관관계는 멤버 -&gt; 상품, 상품 -&gt; 멤버 양쪽에 추가를 해줘야하는데 이를 위해서 <strong>연관관계 편의 메소드</strong>를 추가해서 관리하는 것이 편하다.</p>
<pre><code class="language-java">products.add(product);
product.getMembers().add(this);</code></pre>
<p>양방향 연관관계에서 연관관계 편의 메소드를 추가했듯 다대다 양방향에서도 위처럼 한 군데에서 양쪽 객체(엔티티) 모두에 관계를 설정/업데이트 해주는 것이 좋다.</p>
<h3 id="다대다-매핑의-한계와-극복-연결-엔티티-사용">다대다: 매핑의 한계와 극복, 연결 엔티티 사용</h3>
<p><strong><code>@ManyToMany</code></strong> 사용 시 연결 테이블을 자동으로 처리해주기에 도메인 모델이 단순해지고 여러모로 편리하지만, <strong>실무에서 사용하기에는 한계</strong>가 있다고한다.</p>
<p>예로는 회원이 상품을 주문하면 연결 테이블에 주문 회원, 상품 아이디 외에 주문 수량이나 날짜 등의 컬럼이 필요하다.</p>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/c3006b87-c0a1-4329-ae25-264e4d7a4b27/image.png" alt=""></p>
<p>만약 이렇게 된다면 더 이상 <code>@ManyToMany</code>를 사용할 수 없다. 주문 엔티티나 상품 엔티티에 추가한 컬럼을 매핑할 수 없기 (= 필드의 값으로 가져올 수 있는 방법이 없다)</p>
<p>따라서 연결 테이블에 매핑하는 연결 엔티티를 만들고 추가 컬럼을 해당 엔티티에 매핑해야한다.</p>
<h4 id="member-4">Member</h4>
<pre><code class="language-java">@Entity
public class Member {
    @Id @Column (name = &quot;MEMBER_ID&quot;)
    private String id;

    //역방향
    @OneToMany(mappedBy = &quot;member&quot;)
    private List&lt;MemberProduct&gt; memberProducts;
}</code></pre>
<h4 id="product-1">Product</h4>
<pre><code class="language-java">@Entity
public class Product {
    @Id @Column (name = &quot;PRODUCT_ID&quot;)
    private String id;

    private String name;
}</code></pre>
<p>위의 코드에서는 상품 엔티티 -&gt; 회원상품 엔티티로 탐색 기능이 필요하지 않다고 생각해 연관관계를 두지 않았다.</p>
<h4 id="memberproduct">MemberProduct</h4>
<pre><code class="language-java">@Entity
@IdClass (MemberProductId.class)
public class MemberProduct {
    @Id
    @ManyToOne
    @JoinColumn (name = &quot;MEMEBER_ID&quot;)
    private Member member;

    @Id
    @ManyToOne
    @JoinColumn(name = &quot;PRODUCT_ID&quot;)
    private Product product;

    private int order;
}</code></pre>
<ul>
<li>기본 키를 매핑하는 <code>@Id</code>와 외래 키를 매핑하는 <code>@JoinColumn</code>을 동시에 사용해 기본 키+외래 키를 한 번에 매핑한다.</li>
<li><strong><code>@IdClass</code></strong>를 사용해 <strong>복합 기본 키</strong>를 매핑</li>
</ul>
<br>

<p><strong>복합 기본 키</strong>:
회원 상품 엔티티의 기본키는 <code>MEMBER_ID</code>와 <code>PRODUCT_ID</code> 둘로 이루어진 복합 기본 키이다. JPA에서 복합 키를 사용하기 위해서는 <strong>별도의 식별자 클래스</strong>를 만들고, <strong><code>@IdClass</code></strong>를 사용해 식별자 클래스를 지정해야한다.</p>
<ul>
<li>복합 키는 <strong>별도의 식별자 클래스</strong>로 만들어야함</li>
<li><strong><code>Serializable</code></strong>을 구현해야함</li>
<li><code>equals</code>와 <code>hasCode</code> 메소드를 구현해야함</li>
<li>기본 생성자가 있어야함</li>
<li>식별자 클래스는 public이어야함</li>
<li><code>@IdClass</code>를 사용하는 방법 외에 <code>@EmbeddedId</code>를 사용하는 방법 역시 있다.</li>
</ul>
<h4 id="memberproductid">MemberProductId</h4>
<pre><code class="language-java">public class MemberProductId implements Serializable {
    private String member; //MemberProductId.member와 연결
    private String product; //MemberProduct.product와 연결

    @Override
    public boolean equals (Object o) {...}
    @Override
    public int hashCode() {...}


}</code></pre>
<h4 id="식별-관계">식별 관계:</h4>
<p>회원상품에서 회원과 상품의 기본 키를 받아 자신의 기본 키로 사용하는 경우처럼, <strong>부모 테이블의 기본 키를 받아서 자신의 기본 키 + 외래 키</strong>로 사용하는 것을 데이터베이스 용어로 식별 관계라고 한다.</p>
<h4 id="저장-1">저장</h4>
<pre><code class="language-java">Member member 1 = new Member();
member1.setId(&quot;member1&quot;);
member1.setUserName(&quot;회원1&quot;);
em.persist(member1);

Product productA = new Product();
productA.setId(&quot;productA&quot;);
productA.setName(&quot;상품1&quot;);
em.persist(productA);

MemberProduct memberProdcut = new MemberProdcut();
memberProduct.setMember(member1); //주문회원 연관관계 설정
memberProduct.setProduct(productA);// 주문상품 연관관계 설정
memberProduct.setOrderAmount(2);

em.persist(memberProduct);</code></pre>
<p>위에서는 회원 상품 엔티티를 만들면서 연관된 회원 엔티티와 상품 엔티티를 설정한다. 회원상품 엔티티는 데이터베이스에 저장될 때 연관된 회원의 식별자와 상품의 식별자를 가져와 자신의 기본 키 값으로 사용.</p>
<h4 id="조회">조회</h4>
<pre><code class="language-java">MemberProduct memberProduct = em.find(MemberProduct.class, memberProductId);

Member member = memberProduct.getMember();
Product product = memberProduct.getProduct();</code></pre>
<p><strong>복합 키는 항상 식별자 클래스</strong>를 만들어야한다. 이렇게 생성한 식별자 클래스를 사용해 엔티티를 조회한다. </p>
<h3 id="다대다-새로운-기본-키-사용">다대다: 새로운 기본 키 사용</h3>
<p>복합 키를 사용하기 위해 식별자 클래스를 사용하는 것은 복잡하기에 이를 사용하지 않는 전략 역시 존재한다. <strong>데이터베이스에서 자동으로 생성해주는 대리 키</strong>를 사용하는 것이다.</p>
<p>장점은 간편하고 영구히 쓸 수 있고, 비즈니스에 의존하지 않는다는 점이다. 또한, ORM 시에 복합 키를 만들지 않아도 되기에 위에 있는 문제를 해결할 수 있다.
<img src="https://velog.velcdn.com/images/summeryoung_/post/93e61cfa-9dd4-4786-8f0c-d1e83b8f5128/image.png" alt=""></p>
<p>위를 보면 <code>ORDER_ID</code>라는 새로운 기본 키를 하나 만들고 <code>MEMBER_ID</code>와 <code>PRODUCT_ID</code>는 외래 키로만 사용한다.</p>
<pre><code class="language-java">@Entity
public class Order {
    @Id @GeneratedValue
    @Column (name = &quot;ORDER_ID&quot;)
    private Long Id;

    @ManyToOne
    @JoinColumn (name = &quot;PRODUCT_ID&quot;)
    private Product product;

    private int orderAmount;
}</code></pre>
<p>대리키를 사용하기에 이전에서 본 식별 관계와 복합 키를 사요하는 것보다 매핑이 단순하고 쉽다.</p>
<h4 id="저장-2">저장</h4>
<pre><code class="language-java">//회원과 상품은 이전과 동일
Member member 1 = new Member();
member1.setId(&quot;member1&quot;);
member1.setUserName(&quot;회원1&quot;);
em.persist(member1);

Product productA = new Product();
productA.setId(&quot;productA&quot;);
productA.setName(&quot;상품1&quot;);
em.persist(productA);

//주문 저장
Order order = new Order();
order.setMember(member1); //주문회원 연관관계 설정
order.setProduct(productA);//주문상품 연관관계 설정
order.setOrderAmount(2); //주문 수량
em.persist(order);</code></pre>
<h4 id="조회-1">조회</h4>
<pre><code class="language-java">Long orderId = 1L;
Order order = em.find(Order.class, orderId);

Member member = order.getMember();
Product product = order.getProduct();</code></pre>
<p>식별자 클래스를 사용하지 않으면 훨씬 더 단순한 코드를 통해 저장 및 조회가 가능하다.</p>
<h3 id="다대다-연관관계-정리">다대다 연관관계 정리</h3>
<p>다대다 관계를 일대다&amp;다대일 관계로 풀기 위해서는 <strong>연결 테이블을 만들 때 식별자를 어떻게 구성</strong>할지 선택해야한다.</p>
<blockquote>
<p><strong>식별 관계</strong>: 받아온 식별자를 기본 키 + 외래 키로 사용
<strong>비식별 관계</strong>: 받아온 식별자는 외래 키로만 사용하고, 새로운 식별자를 추가.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[자바 ORM 표준 JPA 프로그래밍] 4주차 스터디]]></title>
            <link>https://velog.io/@summeryoung_/%EC%9E%90%EB%B0%94-ORM-%ED%91%9C%EC%A4%80-JPA-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-4%EC%A3%BC%EC%B0%A8-%EC%8A%A4%ED%84%B0%EB%94%94</link>
            <guid>https://velog.io/@summeryoung_/%EC%9E%90%EB%B0%94-ORM-%ED%91%9C%EC%A4%80-JPA-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-4%EC%A3%BC%EC%B0%A8-%EC%8A%A4%ED%84%B0%EB%94%94</guid>
            <pubDate>Mon, 30 Mar 2026 21:01:11 GMT</pubDate>
            <description><![CDATA[<h1 id="5장-연관관계-매핑-기초">5장. 연관관계 매핑 기초</h1>
<p>엔티티들은 대부분 다른 엔티티와 연관관계가 있다. 이때 자바의 객체는 <strong>참조(주소)</strong>를 사용해 관계를 맺고, 테이블은 <strong>외래 키</strong>를 사용해 관계를 맺는다. 이 둘은 앞에서 나왔듯이 특징이 꽤 다르기에 <strong>객체의 참조와 테이블의 외래 키를 매핑하는 것</strong>이 ORM(객체 관계 매핑)에서 가장 까다로운 부분이다.</p>
<blockquote>
<h3 id="키워드-정리">키워드 정리</h3>
<p><strong>방향</strong>: <strong>단방향</strong>과 <strong>양방향</strong>이 존재. 방향은 <strong>객체관계에만 존재</strong>하고 <strong>테이블 관계는 항상 양방향</strong>이다.
<br> <strong>다중성</strong>: <strong>다대일, 일대다, 일대일, 다대다</strong> 다중성이 존재한다. <br>
<strong>연관관계의 주인</strong>: 객체를 양방향 연관관계로 만들 때에는 연관관계의 주인을 설정해줘야한다.</p>
</blockquote>
<hr>
<h2 id="51-단방향-연관관계">5.1 단방향 연관관계</h2>
<blockquote>
<ul>
<li>회원과 팀</li>
</ul>
</blockquote>
<ul>
<li>회원은 <strong>하나의 팀에만 소속</strong>될 수 있음</li>
<li>회원과 팀은 <strong>다대일</strong>관계
<span style="font-size:15px" >즉, 하나의 팀에는 여러 회원이 속할 수 있지만, 각 회원은 하나의 팀에 속해야함. </span></li>
</ul>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/a492914e-ce52-4ee3-a002-9caca3f81623/image.png" alt=""></p>
<h3 id="객체와-연관관계">객체와 연관관계</h3>
<ul>
<li>회원 객체는 <code>Member.team</code> 필드로 팀 객체와 연관관계를 맺음</li>
<li>회원 객체와 팀 객체는 <strong>단방향 관계</strong>
<span style="font-size:15px" > 멤버 객체에서는 <code>member.getTeam()</code> 과 같이 팀을 알 수 있지만, 반대로 팀에서는 어떤 멤버들이 팀에 들어있는지 확인할 수 없음 </span></li>
<li><strong>객체 그래프 탐색</strong>: 객체에서 참조를 이용해 연관관계를 탐색하는 것을 말함.</li>
</ul>
<h3 id="테이블-연관관계">테이블 연관관계</h3>
<ul>
<li>회원 테이블은 <code>TEAM_ID</code> 외래 키를 통해  팀 테이블과 연관관계를 매음</li>
<li>회원 테이블과 팀 테이블은 <strong>양방향 관계</strong>
<span style="font-size:15px" > 외래키를 사용해 회원과 팀을 조인할 수도 있고, 그 반대 역시 할 수 있다. </span></li>
<li><strong>조인</strong>: 외래키를 이용해 연관관계를 탐색하는 것을 말함.</li>
</ul>
<h4 id="객체와-테이블-연관관계의-차이점">객체와 테이블 연관관계의 차이점</h4>
<p>객체의 참조를 통한 연관관계는 항상 <strong>단방향</strong>이다. 즉, 이 연관관계를 양방향으로 만들기 위해서는 양쪽에 필드를 추가해 참조를 보관해야한다. 즉, 양방향 관계가 아니라 <strong>서로 다른 단방향 관계 2개</strong>라고도 할 수 있다. 이와 다르게 테이블은 외래 키 하나만으로 양방향으로 조인할 수 있다.</p>
<h3 id="객체-관계-매핑">객체 관계 매핑</h3>
<p>Member에서 회원 엔티티를 <strong>매핑</strong>하고, Team에서 팀 엔티티를 <strong>매핑</strong>.</p>
<ul>
<li>객체 연관관계: 회원 객체의 <code>Member.team</code> 필드 사용</li>
<li>테이블 연관관계: 회원 테이블의 <code>MEMBER_TEAM_ID</code> 외래키 컬럼 사용</li>
</ul>
<p>여기서 두 관계의 <code>Member.team</code>과 <code>MEMBER_TEAM_ID</code>를 매핑하는 것이 중요하다</p>
<h4 id="member">Member</h4>
<pre><code class="language-java">@Entity
public class Member {

    @Id
    @Column (name=&quot;MEMBER_ID&quot;)
    private String id;

    private String username;

    //연관관계 매핑
    @ManyToOne
    @JoinColumn(name = &quot;TEAM_ID&quot;)
    private Team team;

    //연관관계 설정
    public void setTeam (Team team) {
        this.team = team;
    }
}
</code></pre>
<h4 id="team">Team</h4>
<pre><code class="language-java">@Entity
public class Team {
    @Id
    @Column
    private String id;

    private String name;
}
</code></pre>
<h4 id="manytoone"><code>@ManyToOne</code></h4>
<p><strong>다대일(N:1)</strong> 관계라는 매핑 정보로 연관관계 매핑 시에는 <strong>다중성</strong>을 나타내는 어노테이션이 필수적이다.</p>
<h4 id="joincolumnnameteam_id"><code>@JoinColumn(name=&quot;TEAM_ID&quot;)</code></h4>
<p>조인 컬럼은 <strong>외래 키를 매핑</strong>할 때 사용하며 <code>name</code> 속성에는 매핑할 외래 키 이름을 지정한다. 해당 어노테이션은 생략이 가능하고, 속성은 아래와 같다.</p>
<ul>
<li><code>name</code>:  매핑할 외래 키 이름</li>
<li><code>referencedColumnName</code>: 외래 키가 참조하는 대상 테이블의 컬럼명</li>
<li><code>unique</code>, <code>nullable</code>, <code>insertable</code>...등</li>
</ul>
<hr>
<h2 id="52-연관관계-사용">5.2 연관관계 사용</h2>
<p>아래는 연관관계를 등록, 수정, 삭제, 조회하는 내용이다.</p>
<h3 id="저장">저장</h3>
<pre><code class="language-java">Team team1 = new Team(&quot;team1&quot;, &quot;팀1&quot;);
em.persist(team1);

Member member1 = new Member(&quot;member1&quot;, &quot;회원1&quot;);
member1.setTeam(team1); //연관관계 설정
em.persist(member1);

Member member2 = new Member(&quot;member2&quot;, &quot;회원2&quot;);
member2.setTeam(team1); //연관관계 설정
em.persist(member2);</code></pre>
<p>회원 엔티티가 팀 엔티티를 참조하고 저장하면, JPA는 참조한 팀의 식별자를 외래키로 사용해 적절한 등록 쿼리를 생성한다.</p>
<h3 id="조회">조회</h3>
<p>연관관계가 있는 엔티티를 조회하는 방법은 크게 2가지 이다</p>
<ul>
<li>객체 그래프 탐색</li>
<li>객체 지향 쿼리 사용 (JPQL)</li>
</ul>
<p><strong>객체 그래프 탐색</strong>
<code>member.getTeam()</code>을 사용해 <code>member</code>와 관련된 <code>team</code> 엔티티를 조회할 수 있음.</p>
<p><strong>객체지향 쿼리(JPQL) 사용</strong>
JPQL도 조인을 지원하는데 SQL과 문법은 조금 다르다.</p>
<pre><code class="language-java">String jpql = &quot;select m from Member m join m.team t where&quot; +
    &quot;t.name=:teamName&quot;;

List&lt;Member&gt; resultList = em.createQuery(jpql, Member.class)
    .setParameter(&quot;teamName&quot;, &quot;팀1&quot;);

for (Member member : resultList) {
    System.out.println(&quot;[query] member.username=&quot; 
        + member.getUsername());
}</code></pre>
<p><strong><code>from Member m join m.team t</code></strong>
 :회원이 팀과 관계를 가지고 있는 <strong>필드(<code>m.team</code>)</strong>를 통해 <code>Member</code>와 <code>Team</code>을 조인하였다. 이후 <code>where</code>절을 통해서 조인한 <code>t.name</code>을 검색조건으로 사용해 팀1에만 속한 팀을 검색한 것이다.</p>
<p> <strong><code>:teamName</code></strong>
 : <code>:</code>로 시작하는 것은 <strong>파라미터를 바인딩받는 문법</strong>이다.</p>
<h3 id="수정">수정</h3>
<pre><code class="language-java">Team team2 = new Team(&quot;team2&quot;, &quot;팀2&quot;);
em.persist(team2);

Member member = em.find(Member.class, &quot;member1&quot;);
member.setTeam(team2);</code></pre>
<p>수정의 경우에는 다른 메소드가 없기 때문에 엔티티를 <strong>조회</strong>한 후에 엔티티의 값을 변경해두면 트랜잭션 커밋 시에 플러시가 일어나며 변경 감지 기능이 작동한다.</p>
<h3 id="연관관계-제거">연관관계 제거</h3>
<p>회원1을 팀에 소속하지 않도록 연관관계를 제거</p>
<pre><code class="language-java">Member member1 = em.find(Member.class, &quot;member1&quot;);
member1.setTeam(null); //연관관계 제거</code></pre>
<p>엔티티를 조회한 후 수정하여 연관관계를 제거한다.</p>
<h3 id="연관된-엔티티-삭제">연관된 엔티티 삭제</h3>
<p>연관된 엔티티를 삭제하기 위해서는 <strong>기존의 연관관계를 먼저 제거</strong>한 후에 삭제해야한다. 그렇지 않으면 <strong>외래 키 제약조건</strong>으로 인해 데이터베이스에서 오류가 발생한다.</p>
<p>예를 들면 팀1에 회원1,2가 소속되어있다고 할 때, 팀1을 삭제하기 위해서는 이 둘 사이의 연관관계를 끊어줘야한다.</p>
<pre><code class="language-java">member1.setTeam(null);
member2.setTeam(null);
em.remove(team);</code></pre>
<hr>
<h2 id="53-양방향-연관관계">5.3 양방향 연관관계</h2>
<p>위에서는 회원에서 팀으로만 접근하는 <strong>다대일 단방향</strong> 매핑을 만들었다. 해당 장에서는 반대인 팀에서 회원으로 접근하는 관계를 추가해본다.</p>
<p>회원과 팀이 <strong>다대일 관계</strong>인데 반해, 팀과 회원은 <strong>일대다 관계</strong>이다. 여러 연관관계를 맺을 수 있기에 <strong>컬렉션</strong>을 사용해야한다. 
<span style="color:gray; font-size:15px" > 팀1에 여러 명의 회원들이 속해있을 수 있기에, 이 여러 명의 회원들을 관리하기 위해서 리스트/컬렉션을 사용해야한다. </span></p>
<p>데이터베이스 입장에서는 원래부터 외래 키 하나로 양방향 조회가 가능하기에 추가할 부분이 없다.</p>
<h3 id="양방향-연관관계-매핑">양방향 연관관계 매핑</h3>
<pre><code class="language-java">@Entity
public class Team {

    @Id
    @Column(name = &quot;TEAM_ID&quot;)
    private String id;

    private String name;

    @OneToMany (mappedBy = &quot;team&quot;)
    private List&lt;Member&gt; members = new ArrayList&lt;Member&gt;();
}</code></pre>
<h4 id="onetomany"><code>@OneToMany</code>:</h4>
<p>팀과 회원은 <strong>일대다 관계</strong>이기에, 팀 엔티티에는 컬렉션인 <code>List&lt;Member&gt; members</code>를 추가하고 어노테이션 역시 위의 <code>@OneToMany</code>를 사용한다.</p>
<p>여기서 <code>mappedBy</code> 속성은 <strong>양방향 매핑</strong>일 경우 사용하는 것으로 <strong>반대쪽 매핑의 필드 이름</strong>을 값으로 주면 된다. 위에서는 반대의 매핑이 <code>Member.team</code>이므로 <code>team</code>을 준다.</p>
<h3 id="일대다-컬렉션-조회">일대다 컬렉션 조회</h3>
<p>팀에서 회원 컬렉션(리스트)로 객체 그래프 탐색을 사용해 조회한 회원들을 출력하게된다</p>
<pre><code class="language-java">Team team = em.find(Team.class, &quot;team1&quot;);
List&lt;Member&gt; members = team.getMembers(); // 팀-&gt;회원 객체 그래프 탐색

for (Member member : members) {
    System.out.println(&quot;member.username = &quot;+ member.getUsername());
}</code></pre>
<hr>
<h2 id="54-연관관계의-주인">5.4 연관관계의 주인</h2>
<p><code>mappedBy</code>를 보면 회원 엔티티를 매핑할 때에는 사용하지 않은 반면, 팀 엔티티의 매핑에서만 사용하였다. 이 필요성을 해당 장에서 설명한다.</p>
<p>앞서 설명된 <strong>객체와 테이블</strong>의 연관관계 차이 때문에 JPA에서는 두 객체 연관관계 중 하나를 정해 <strong>테이블의 외래키를 관리하기 위한 연관관계의 주인</strong>을 정하게된다.</p>
<h3 id="양방향-매핑의-규칙-연관관계의-주인">양방향 매핑의 규칙: 연관관계의 주인</h3>
<p>양방향 연관관계 매핑 시 <strong>두 연관관계 중 하나를 연관관계의 주인으로 정해야한다.</strong> </p>
<ul>
<li>연관관계의 주인만 데이터베이스 연관관계와 매핑.</li>
<li>외래키를 관리(등록, 수정, 삭제). </li>
<li>주인이 아닌 쪽은 읽기만 가능하다.</li>
</ul>
<p>이 주인을 정하기 위해 <code>mappedBy</code> 속성을 사용하게된다.</p>
<ul>
<li>주인은 <code>mappedBy</code> 속성을 사용하지 않는다</li>
<li>주인이 아니라면, <code>mappedBy</code>를 통해 <strong>연관관계 주인</strong>을 저장해야함</li>
</ul>
<p>즉, <strong>연관관계의 주인을 정하는 것 = 외래 키 관리자 설정</strong>과도 같다. 앞서서는 <code>Member</code>가 키 관리자이자 연관관계의 주인이다.</p>
<h3 id="연관관계의-주인--외래키가-있는-곳">연관관계의 주인 = 외래키가 있는 곳</h3>
<p>위의 코드에서는 회원 테이블이 외래키를 갖고 있기에 <code>Member.team</code>이 이 연관관계의 주인이 된다. </p>
<ul>
<li>연관관계 주인이 아닌 <code>Team.members</code>에는 <code>mappedBy = &quot;team&quot;</code> 속성을 사용해 주인이 아님을 설정</li>
<li><code>mappedBy</code>의 값은 연관관계 주인인 엔티티의 필드</li>
<li>연관관계의 주인이 아닌 팀은 <strong>외래키를 읽기만 가능하고 변경하지 못함</strong></li>
</ul>
<pre><code class="language-java">@OneToMany (mappedBy = &quot;team&quot;)
private List&lt;Member&gt; members = new ArrayList&lt;&gt;();
</code></pre>
<hr>
<h2 id="55-양방향-연관관계-저장">5.5 양방향 연관관계 저장</h2>
<p>양방향 연관관계에서 연관관계의 주인이 외래 키를 관리하기에 <strong>주인이 아닌 방향은 값을 설정하지 않아도, 데이터베이스에 외래 키 값이 정상 입력</strong>된다.</p>
<pre><code class="language-java">team1.getMembers().add(member1); //무시</code></pre>
<p>즉, 위와 같은 코드가 필요하다고 생각되어도 결국 무시되고, 이 값은 외래 키 값에 영향을 주지 못한다.</p>
<hr>
<h2 id="56-양방향-연관관계의-주의점">5.6 양방향 연관관계의 주의점</h2>
<p><strong>연관관계의 주인에는 값을 입력하지 않고, 주인이 아닌 곳에만 값을 입력</strong>하는 것. 데이터베이스에 외래 키 값이 정상적으로 저장되지 않기에 주의해야한다.</p>
<p>이 경우 외래키에 널값이 입력된다.</p>
<h3 id="순수한-객체까지-고려한-양방향-연관관계">순수한 객체까지 고려한 양방향 연관관계</h3>
<p>어차피 연관관계의 주인이 아닌쪽에는 값을 저장해도 데이터베이스에 반영되지 않기에 저장할 필요가 없다. 하지만, <strong>객체 관점에서는 양쪽 방향에 모두 값을 입력하는 것이 가장 안전하다.</strong></p>
<pre><code class="language-java">...
member1.setTeam(team1); //연관관계: member1 -&gt; team1
team1.getMembers().add(member1); // 연관관계: team1 -&gt; member1

member2.setTeam(team1); //연관관계: member2 -&gt; team1
team1.getMembers().add(member2); //연관관계: team1 -&gt; member2</code></pre>
<p>이렇게 양쪽 모두에 관계를 설정해준 경우에만 후에 필요에 의해 팀에 있는 인원 수를 출력한다거나, 팀에 있는 팀원 목록을 출력할 때 객체로 접근할 수 있다.</p>
<h4 id="전체">전체</h4>
<pre><code class="language-java">Team team1 = new Team(&quot;team1&quot;, &quot;팀1&quot;);
em.persist(team1);

Member member1 = new Member(&quot;member1&quot;, &quot;회원1&quot;);

//양방향 연관관계 설정
member1.setTeam(team1); //연관관계 설정 member1 -&gt; team1
team1.getMembers().add(member1); //연관관계 설정 team1 -&gt; member1
em.persist(member1); //영속상태로 만듦.</code></pre>
<p>양쪽에 연관관계를 위처럼 설정해야 <strong>순수 객체 상태</strong>에서도 동작하며, 테이블의 <strong>외래 키에도 정상 입력</strong>된다. 테이블의 외래 키 값은 연관관계 주인인 <code>Member.team</code>의 값이 사용된다.</p>
<h3 id="연관관계-편의-메소드">연관관계 편의 메소드</h3>
<p>양방향 연관관계에서는 결국 명시적으로 양쪽에 값을 써주고 관리하는 불편함이 있고, 실수의 여지가 많다.</p>
<pre><code class="language-java">member.setTeam(team);
team.getMembers().add(member);</code></pre>
<p>양방향 관계에서 두 코드를 하나처럼 사용하는 것이 안전하기에 <code>Member</code> 클래스의 메소드를 수정해 코드를 리팩토링할 수 있다.</p>
<pre><code class="language-java">public class Member{
    private Team team;

    public void setTeam(Team team) {
        this.team = team.
        team.getMembers().add(this);
    }
}</code></pre>
<p>메소드 하나로 양쪽에 모두 값을 설정하도록 하면 실수나 빼먹을 가능성이 줄어든다.</p>
<h3 id="연관관계-편의-메소드-작성-시-주의사항-연관관계-제거">연관관계 편의 메소드 작성 시 주의사항: 연관관계 제거</h3>
<p>앞선 <code>setTeam()</code> 메소드에서는 팀을 변경해주어도 이전 팀의 멤버에서 변경한 팀원을 조회할 수 있다는 문제가 존재한다.</p>
<pre><code class="language-java">member1.setTeam(teamA);
member1.setTeam(teamB);
Member findMember = teamA.getMember(); //여전히 멤버1 조회 가능</code></pre>
<p>즉, 팀을 변경할 때 <strong>관계를 제거하지 않았다</strong>. 연관관계를 <strong>변경할 때는 기존 연관관계를 삭제</strong>하는 코드를 추가해줘야한다. 따라서 앞의 <code>setTeam()</code>을 아래와 같이 수정해줘야한다.</p>
<pre><code class="language-java">public void setTeam(Team team) {
    //이전의 연관관계 삭제 (팀이 있었다면 해당 팀의 컬렉션에서 멤버 삭제
    if (this.team != null) {
        this.team.getMembers().remove(this);
    }

    this.team = team;
    team.getMembers().add(this);
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[자바 ORM 표준 JPA 프로그래밍] 3주차 스터디]]></title>
            <link>https://velog.io/@summeryoung_/%EC%9E%90%EB%B0%94-ORM-%ED%91%9C%EC%A4%80-JPA-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-3%EC%A3%BC%EC%B0%A8-%EC%8A%A4%ED%84%B0%EB%94%94</link>
            <guid>https://velog.io/@summeryoung_/%EC%9E%90%EB%B0%94-ORM-%ED%91%9C%EC%A4%80-JPA-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-3%EC%A3%BC%EC%B0%A8-%EC%8A%A4%ED%84%B0%EB%94%94</guid>
            <pubDate>Sun, 29 Mar 2026 07:41:37 GMT</pubDate>
            <description><![CDATA[<h1 id="4장-엔티티-매핑">4장. 엔티티 매핑</h1>
<p>JPA에서는 <strong>매핑 어노테이션</strong>을 사용해 엔티티와 테이블을 매핑한다. 다양한 매핑 어노테이션들은 아래와 같이 구분해 볼 수 있다. </p>
<blockquote>
<ul>
<li>객체와 테이블 매핑: <code>@Entity</code>, <code>@Table</code></li>
</ul>
</blockquote>
<ul>
<li>기본 키 매핑: <code>@Id</code></li>
<li>필드와 컬럼 매핑: <code>@Column</code></li>
<li>연관관계 매핑: <code>@ManyToOne</code>, <code>@JoinColumn</code></li>
</ul>
<p>매핑 정보는 어노테이션 외에도 XML을 사용해 구성할 수도 있다고 한다.</p>
<hr>
<h2 id="41-entity">4.1 @Entity</h2>
<h4 id="entity"><code>@Entity</code>:</h4>
<p>JPA를 사용해 <strong>테이블과 매핑할 클래스</strong>에 붙이는 어노테이션으로 필수적이다. 이 어노테이션이 붙은 클래스는 JPA가 관리를 하게 된다.</p>
<pre><code class="language-java">@Entity
public class Member {}</code></pre>
<table>
<thead>
<tr>
<th align="center">속성</th>
<th align="center">기능</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><strong>name</strong></td>
<td align="center">JPA에서 사용할 <strong>엔티티 이름</strong>을 정함. 기본값은 클래스 이름.</td>
</tr>
</tbody></table>
<p><code>@Entity</code> 어노테이션 사용 시 주의사항이 있다.</p>
<ul>
<li><strong>기본 생성자</strong> 필수</li>
<li>final 클래스나 enum, interface, inner 클래스에는 사용 불가</li>
<li>저장할 필드에 final 사용 불가</li>
</ul>
<hr>
<h2 id="42-table">4.2 @Table</h2>
<h4 id="table"><code>@Table</code>:</h4>
<p>엔티티와 <strong>매핑할 테이블</strong>을 지정해주는 어노테이션이다. 생략될 시에는 매핑한 엔티티 이름을 테이블 이름으로 사용한다.</p>
<table>
<thead>
<tr>
<th align="center">속성</th>
<th align="center">기능</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><strong>name</strong></td>
<td align="center">매핑할 테이블의 이름</td>
</tr>
<tr>
<td align="center">catalog</td>
<td align="center">catalog 기능이 있는 DB에서 catalog를 매핑</td>
</tr>
<tr>
<td align="center">schema</td>
<td align="center">schema 기능이 있는 DB에서 schema를 매핑</td>
</tr>
</tbody></table>
<pre><code class="language-java">@Entity
@Table(name=&quot;MEMEBER&quot;)
public class Member {}</code></pre>
<hr>
<h2 id="43-다양한-매핑-사용">4.3 다양한 매핑 사용</h2>
<blockquote>
<h4 id="요구사항">요구사항</h4>
</blockquote>
<ol>
<li>회원은 일반 회원과 관리자로 구분</li>
<li>회원 가입일과 수정일 존재</li>
<li>회원을 설명할 수 있는 필드 존재 (길이 제한 없음)</li>
</ol>
<pre><code class="language-java">@Entity
@Table(name=&quot;MEMBER&quot;)
public class Member {
    @Id
    @Column(name = &quot;ID&quot;)
    private String id;

    @Column(name=&quot;NAME&quot;)
    private String username;

    private Integer age;

    @Enumerated(EnumType.STRING)
    private RoleType roleType;

    @Temporal (TemporalType.TIMESTAMP)
    private Date createdDate;

    @Temporal (TemporalType.TIMESTAMP)
    private Date lastModifiedDate;

    @Lob
    private String description;
}

public enum RoleType {
    ADMIN, USER
}</code></pre>
<h4 id="enumerated"><strong><code>@Enumerated</code></strong>:</h4>
<p>자바의 <code>enum</code>을 사용해 회원의 타입을 구분한다. <strong>자바의 <code>enum</code>을 사용하려면 <code>@Enumerated</code> 어노테이션으로 매핑해야한다.</strong></p>
<h4 id="temporal"><code>@Temporal</code>:</h4>
<p>자바의 날짜 타입을 사용할 때 매핑하는 어노테이션.</p>
<h4 id="lob"><code>@LOB</code>:</h4>
<p>CLOB, BLOB 타입의 매핑을 위해 사용하는 어노테이션. 위에서는 길이 제한이 없는 회원 설명 필드의 매핑을 위해 사용됐다.</p>
<hr>
<h2 id="44-데이터베이스-스키마-자동-생성">4.4 데이터베이스 스키마 자동 생성</h2>
<p>JPA에서는 클래스 매핑 정보를 통해 어떤 테이블의 어떤 컬럼을 사용하는지 알 수 있다. 이 매핑 정보와 데이터베이스 방언을 사용해 JPA에서는 <strong>데이터베이스 스키마를 생성</strong>할 수 있다.</p>
<p>스키마 자동 생성 기능을 사용하기 위해서는 persistence.xml에 아래의 속성을 추가해줘야한다.</p>
<pre><code class="language-xml">&lt;property name = &quot;hibernate.hbm2ddl.auto&quot; value=&quot;create&quot; /&gt;</code></pre>
<p>해당 속성은 애플리케이션 실행 시점에 <strong>데이터베이스 테이블을 자동으로 생성</strong>하는데, 이때 기존의 테이블을 <strong>삭제</strong>하고 <strong>다시 생성</strong>한다. 위의 속성에는 <code>create</code>, <code>create-drop</code>, <code>update</code>, <code>validate</code>, <code>none</code>이 존재한다. </p>
<p>다만 DLL을 수정하는 옵션은 운영서버에서 절대 사용하면 안된다. 운영 중인 데이터베이스의 테이블이나 컬럼을 삭제하는 일이 벌어질 수 있다.</p>
<p>스키마 자동 생성을 통해 자동으로 생성되는 DDL(데이터 정의어)는 지정한 데이터베이스 방언에 따라 달라진다. 다만, 스키마 자동 생성 기능의 DDL은 완벽하지 않으니 운영환경에서의 사용은 지양하는 것이 좋다.</p>
<hr>
<h2 id="45-ddl-생성-기능">4.5 DDL 생성 기능</h2>
<p>스키마 자동 생성을 통해 만들어지는 DDL에 <strong>제약조건</strong>을 추가할 수 있다.</p>
<pre><code class="language-java">@Entity
@Table (name=&quot;MEMBER&quot;, 
        uniqueConstraints = {@UniqueConstraint(
            name = &quot;NAME_AGE_UNIQUE&quot;,
            columnNames = {&quot;NAME&quot;, &quot;AGE&quot;} 
       )})
public class Member {
    @Id
    @Column (name=&quot;ID&quot;)
    private String id;

    @Column(name=&quot;NAME&quot;, nullable=false, length=10)
    private String username;
}</code></pre>
<ul>
<li><code>nullable</code>: 해당 컬럼에 <code>null</code>이 들어가지 않도록 설정할 수 있음. (<code>not null</code>)</li>
<li><code>length</code>: 해당 컬럼의 데이터의 길이를 제한하는 제약 조건을 설정할 수 있음.</li>
<li><code>uniqueConstraints</code>: 유니크 제약조건을 설정. 위의 코드에서는 NAME과 AGE를 유니크한 컬럼이 되도록 설정.</li>
</ul>
<hr>
<h2 id="46-기본-키-매핑">4.6 기본 키 매핑</h2>
<h4 id="id"><code>@Id</code></h4>
<p>기본키(primary key)의 매핑을 위해 사용하는 어노테이션으로. 기본 키를 <strong>애플리케이션에서 직접 할당</strong>할 때 사용한다.</p>
<blockquote>
<h4 id="jpa-제공-데이터베이스-기본--키-생성-전략">JPA 제공 데이터베이스 기본  키 생성 전략</h4>
</blockquote>
<ol>
<li><strong>직접 할당</strong>: 기본 키를 애플리케이션에서 직접 할당</li>
<li><strong>자동 생성</strong>: 대리 키 사용 방식<ul>
<li><code>IDENTITY</code>: 기본 키 생성을 데이터베이스에 위임</li>
<li><code>SEQUENCE</code>: 데이터베이스 시퀀스를 사용해 기본 키를 할당</li>
<li><code>TABLE</code>: 키 생성 테이블을 이용</li>
</ul>
</li>
</ol>
<p>자동 생성 전략이 다양한 이유는 데이터베이스 벤더마다 지원하는 방식이 다르기 때문. MySQL은 기본 키 값을 자동으로 채우는 <code>AUTO_INCREMENT</code> 기능을 제공.</p>
<h3 id="1-기본-키-직접-할당-전략">(1) 기본 키 직접 할당 전략</h3>
<p><code>@Id</code>: 기본 키 직접 할당을 위해서는 해당 어노테이션을 사용하면 된다. 적용 가능한 자바 타입으로는 기본형, 래퍼(Wrapper)형,  문자열, 날짜 등이 있다.</p>
<p>해당 방식은 <code>em.persist()</code>를 사용해 엔티티를 저장하기 전 <strong>애플리케이션에서 기본 키를 직접 할당</strong>하는 방식이다.</p>
<pre><code class="language-java">Board board = new Board();
board.setId(&quot;id1&quot;);
em.persist(board);</code></pre>
<p>만약 식별자 값 없이 저장하게 되면 예외가 발생한다.</p>
<h3 id="2-identity-전략">(2) IDENTITY 전략</h3>
<p>IDENTITY 전략은 기본 키 생성을 <strong>데이터베이스에 위임</strong>하는 전략이다. 
<span style="color:gray; font-size:80%"> 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용한다고한다. 예를 들면 MySQL의 <code>AUTO_INCREMENT</code> 기능을 통해 데이터베이스에서 식별자 값을 할당하지 않아도 자동으로 생성해준다. </span></p>
<p>데이터베이스에서 값을 저장할 때 식별자 값을 저장하지 않아도 순서대로 값을 채워주게된다.</p>
<p>해당 전략은 데이터베이스에 값을 저장하고 나서야 기본 키 값을 구할 수 있을 때 사용한다. 직접 할당 전략과는 다르게 <strong><code>@Id</code> 어노테이션과 함께 <code>@GeneratedValue</code> 어노테이션을 사용하고 식별자 생성 전략을 선택</strong>해야할 필요성이 있다. </p>
<p>IDENTITY 전략에서는 <code>@GeneratedValue</code>의 strategy 속성 값을 <strong><code>GenerationType.IDENTITY</code></strong>로 지정하면 된다. JPA에서는 그럼 기본 키 값을 얻어오기 위해 데이터베이스를 추가로 조회하게된다.</p>
<pre><code class="language-java">@Id
@GeneratedValue( strategy = GenerationType.IDENTITY)
private Long id;</code></pre>
<p>이때 식별자 값은 데이터를 데이터베이스에 저장하는 시점에 데이터베이스가 생성한 값을 JPA가 조회하는 식으로 얻어온다. </p>
<p>영속 상태가 되려면 식별자 값이 필요하기에 이 전략에서는 <code>em.persist()</code> 호출 즉시 INSERT SQL이 데이터베이스에 전달되어, 트랜잭션을 지원하는 쓰기 지연이 동작하지 않는다.</p>
<h3 id="3-sequence-전략">(3) SEQUENCE 전략</h3>
<p>데이터베이스 시퀀스는 <strong>유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트</strong>이다. 해당 전략을 사용하기 위해서는 시퀀스를 생성해야한다.
<span style="color:gray; font-size:80%"> 오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용할 수 있다.</span></p>
<pre><code class="language-sql">CREATE SEQUENCE BOARD_SEQ START WITH 1 INCREMENT BY 1;</code></pre>
<p>위 처럼 시퀀스를 생성하고,</p>
<pre><code class="language-java">@SequenceGenerator (
    name = &quot;BOARD_SEQ_GENERATOR&quot;,
    sequenceName = &quot;BOARD_SEQ&quot;, //매핑할 데이터베이스 시퀀스 이름
    initialValue = 1, allocationSize = 1)

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
                generator = &quot;BOARD_SEQ_GENERATOR&quot;)
private Long id;</code></pre>
<h4 id="sequencegenerator"><code>@SequenceGenerator</code>:</h4>
<p>시퀀스 생성기를 등록하기 위한 어노테이션으로 사용할 <strong>데이터베이스 시퀀스 매핑</strong>을 위해 사용한다. <strong><code>sequenceName</code></strong>을 앞에서 생성한 데이터베이스 시퀀스의 이름으로 지정한다.</p>
<p>JPA에서는 이 <strong>시퀀스 생성기</strong>를 실제 데이터베이스의 <code>BOARD_SEQ</code> 시퀀스와 매핑하게된다. </p>
<p>시퀀스 전략의 경우 <code>em.persist()</code> 호출 시 데이터베이스 시퀀스를 사용해 식별자를 조회한다. 이렇게 조회한 식별자를 엔티티에 할당하고 이 엔티티를 영속성 컨텍스트에 저장하기에, 이후 <strong>트랜잭션을 커밋하고 플러시할 때 엔티티를 데이터베이스에 저장</strong>할 수 있다.</p>
<h3 id="4-table-전략">(4) TABLE 전략</h3>
<p>테이블 전략은 키 생성 전용 테이블을 하나 만들어 이름과 값으로 사용할 컬럼을 만들어 데이터베이스 시퀀스를 흉내내는 전략이다. 이 경우에도 시퀀스 전략처럼 키 생성 용도로 사용할 테이블을 먼저 만들어야한다.</p>
<pre><code class="language-java">@TableGenerator (
    name = &quot;BOARD_SEQ_GENERATOR&quot;,
    table = &quot;MY_SEQUENCES&quot;,
    pkColumnValue = &quot;BOARD_SEQ&quot;, allocationSize = 1
)

@Id
@GeneratedValue (strategy = GenerationType.TABLE,
                generator = &quot;BOARD_SEQ_GENERATOR&quot;)
private Long id;</code></pre>
<h4 id="tablegenerator"><code>@TableGenerator</code>:</h4>
<p>테이블 키 생성기를 등록하기 위한 어노테이션. 테이블 키 생성기의 이름을 등록하고, 미리 생성해둔 키 생성용 테이블을 매핑한다.</p>
<p>테이블 전략 사용을 위해서는 <code>@GeneratedValue</code>의 strategy를 위처럼 설정해줘야한다.</p>
<p><span style="color:gray; font-size:80%"> 처음에 읽었을 때는 감이 잘 안왔는데 결과 테이블을 살펴보니, 특정 테이블 하나만을 위한 키가 아니라 여러 테이블의 키를 한 번에 여기서 관리하는 느낌이다. 예를 들면 게시글 테이블의 다음 값이 5, 댓글은 10, 이런 식으로 모아서 관리하고 여기서 찾아서 기본 키를 할당하는 듯 하다.</span></p>
<h3 id="5-auto-전략">(5) AUTO 전략</h3>
<p><code>GenerationType.AUTO</code>를 사용해 전략을 지정해주면 데이터베이스 방언에 따라 앞서 나온 <code>IDENTITY</code>, <code>SEQUENCE</code>, <code>TABLE</code> 전략 중 하나를 자동으로 선택하게된다.</p>
<p>이 전략의 장점은 <strong>데이터베이스를 변경해도 코드를 수정할 필요가 없다</strong>는 점이다. </p>
<p>다만, <code>SEQUENCE</code>나 <code>TABLE</code> 전략이 선택될 경우에는 시퀀스나 테이블을 미리 만들어두어야한다. 이 부분도 스키마 자동 생성기능을 사용하면 하이버네이트에서 기본값을 사용해 적절한 시퀀스나 테이블을 만들어주긴한다.</p>
<blockquote>
<h4 id="정리">정리</h4>
</blockquote>
<ul>
<li>직접할당: <code>em.persist()</code> 호출 전 애플리케이션에서 식별자 값을 직접 할당. 없을 경우 예외 발생</li>
<li>SEQUENCE: 시퀀스에서 식별자 값을 획득 후 영속성 컨텍스트에 저장</li>
<li>TABLE: 시퀀스 생성용 테이블에서 식별자 값 획득 후 영속성 컨텍스트에 저장</li>
<li>IDENTITY: 데이터베이스에 엔티티를 저장해 식별자 값 획득 후 영속성 컨텍스트에 저장</li>
</ul>
<p>식별자 선택을 할 때에는 <strong>자연 키 보다는 대리 키를 사용</strong>하는 편이 좋다고 한다. 자연 키는 아무리 잘 고른다 해도 예기치 못하게 변경이 일어날 수 있는 상황이 많고, 널값이 들어가는 경우도 있기 때문이다.</p>
<hr>
<h2 id="47-필드와-컬럼-매핑">4.7 필드와 컬럼 매핑</h2>
<h4 id="column"><code>@Column</code></h4>
<p><strong>객체 필드를 테이블의 컬럼에 매핑</strong>하는 어노테이션으로 가장 많이 사용. 속성 중에서는 <code>name</code>, <code>nullable</code>이 주로 사용된다.</p>
<h4 id="enumerated-1"><code>@Enumerated</code></h4>
<p><strong>자바의 <code>enum</code> 타입</strong>을 매핑할 때 사용된다.</p>
<ul>
<li><code>EnumType.ORDINAL</code>: enum에 정의된 순서대로 데이터베이스에 인덱스 같은 값이 저장된다.</li>
<li><code>EnumType.STRING</code>: enum에 정의된 이름이 그대로 데이터베이스에 저장된다. 수정이나 순서 변경 등이 발생해도 안전하다는 장점이 존재하지만 저장되는 크기가 크다.</li>
</ul>
<h4 id="temporal-1"><code>@Temporal</code></h4>
<p><strong>날짜 타입(<code>java.util.Date</code>, <code>java.tuil.Calendar</code>)을 매핑</strong>할 때 사용된다. 해당 어노테이션 생략 시 자바의 <code>Date</code>와 유사한 <code>timestamp</code>로 정의되게 된다.</p>
<h4 id="lob-1"><code>@Lob</code></h4>
<p>지정할 수 있는 속성이 없다. 다만, <strong>매핑 필드 타입이 문자면 CLOB으로 나머지는 BLOB</strong>으로 매핑된다.</p>
<h4 id="transient"><code>@Transient</code></h4>
<p>해당 필드는 <strong>매핑을 하지 않는다.</strong> 데이터베이스에 저장하지 않고 조회하지 도 않는, 객체에서 임시로 어떤 값을 보관하기 위해서 사용하는 어노테이션이다.</p>
<h4 id="access"><code>@Access</code></h4>
<p>JPA가 엔티티의 데이터에 접근하는 방식을 지정한다.</p>
<ul>
<li>필드 접근: <code>AccessType.FIELD</code>로 지정하며 필드에 직접 접근한다.</li>
<li>프로퍼티 접근: <code>AccessType.PROPERTY</code>로 지정하며 <strong>접근자</strong>(<code>getter</code>)를 사용한다.</li>
</ul>
<pre><code class="language-java">//필드 접근
@Entity
@Access (AccessType.FIELD)
public class Member{
    @Id
    private String id;
}</code></pre>
<p>위의 경우 <code>@Id</code>를 필드에 붙여주었기에 <code>@Access (AccessType.FIELD)</code>로 설정한 것과 같기에 어노테이션을 생략해도 된다.</p>
<pre><code class="language-java">//프로퍼티 접근
@Entity
@Access (AccessType.PROPERTY)
public class Member{
    private String id;

    private String data1;

    @Id
    public String getData1() {
        return data1;
    }
}</code></pre>
<p>위의 경우 <code>@Id</code>를 프로퍼티에 붙여주었기에 <code>@Access (AccessType.PROPERTY)</code>로 설정한 것과 같아 어노테이션을 생략해도 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터베이스 4주차 SQL 문제 풀이]]></title>
            <link>https://velog.io/@summeryoung_/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-4%EC%A3%BC%EC%B0%A8-SQL-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4</link>
            <guid>https://velog.io/@summeryoung_/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-4%EC%A3%BC%EC%B0%A8-SQL-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4</guid>
            <pubDate>Fri, 27 Mar 2026 10:27:57 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="q1">Q1.</h3>
</blockquote>
<h4 id="프로그래머스-클래스-문제-등록한-방이-2개-이상인-헤비-유저의-등록-건수-출력">프로그래머스 클래스 문제: 등록한 방이 2개 이상인 헤비 유저의 등록 건수 출력</h4>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/6d54e4ee-c2c3-4731-818a-f5d5fdbf66a2/image.png" alt=""></p>
<p>처음에 서브쿼리를 안쓰고 문제를 풀 수 있나 생각해봤는데 <code>HOST_ID</code>를 기준으로 그룹을 만들어서 <code>COUNT</code>를 해야하는데 그룹을 만든 이상, 해당 호스트 아이디의 다른 튜플들을 출력할 수 없을 것 같아 결국 서브 쿼리를 사용했다.</p>
<p>하위 쿼리에서 우선 <code>HOST_ID</code>를 기준으로 그룹을 만들었을 때 <code>COUNT(*)</code>의 개수가 2 이상인 것들의 <code>HOST_ID</code>를 선택해 이 호스트 아이디와 일치하는 튜플들만 상위 쿼리에서 출력하도록 하였다.</p>
<p>처음에 <code>HOST_ID</code>가 아니라 <code>ID</code>를 하위 쿼리에서 셀렉트해서 잘못 출력했었다. <code>ID</code>를 기준으로하면, <code>HOST_ID</code>로 그룹화했을 때 하나의 등록 ID만 남으니 그렇게 하면 안됐던 것 같다.</p>
<ul>
<li>풀고 나서 찾아보니 <code>EXISTS</code> 로도 풀 수 있다는거 같아서 찾아보고 풀어보려했는데? 약간 감이 잘 안왔다. 그냥 서브쿼리가 아니라 상관 부속질의(그러니까 상하 관계있는? 서로 연결된?) 그런 서브쿼리가 필요했다.</li>
</ul>
<pre><code class="language-sql">SELECT ID, NAME, HOST_ID
FROM PLACES P1
WHERE EXISTS (
    SELECT *
    FROM PLACES P2
    GROUP BY HOST_ID
    HAVING COUNT(*) &gt;= 2
)
ORDER BY ID;</code></pre>
<p>처음에는 위에처럼 썼는데 의외로 에러는 안났다. 대신 당연히 답은 틀렸는데 이유를 찾아보니 <strong>두 쿼리 사이의 연결관계</strong>가 설정되지 않아서 였다. 쿼리를 보면, 상위 쿼리의 행을 가지고 들어가지 않으니 하위 쿼리에서 전체 <code>PLACES</code> 테이블을 <code>HOST_ID</code>를 기준으로 그룹화하고 하나라도 <code>HAVING</code>의 조건을 만족하면 참을 반환하고 있었다.</p>
<pre><code class="language-sql">SELECT * 
FROM PLACES P1 
WHERE EXISTS ( 
    SELECT * 
    FROM PLACES P2 
    WHERE P1.HOST_ID = P2.HOST_ID 
    GROUP BY HOST_ID 
    HAVING COUNT(*) &gt;= 2 ) 
ORDER BY ID;</code></pre>
<p>이걸 고쳐서 위에처럼 연결관계를 설정해주었다. <code>WHERE</code> 절을 사용해서 상위 쿼리에서 가지고 들어온 행의 <code>HOST_ID</code>와 일치하는 경우의 전체 등록 수만을 기준으로 <code>EXISTS</code>를 판단할 수 있도록 수정했다.</p>
<hr>
<blockquote>
<h3 id="q2">Q2.</h3>
</blockquote>
<h4 id="프로그래머스-클래스-문제-입양되지-않은-동물-중-보호소에-들어온지-오래된-3마리-출력">프로그래머스 클래스 문제: 입양되지 않은 동물 중 보호소에 들어온지 오래된 3마리 출력</h4>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/91197d83-ced0-4ac9-9121-2b5c1dfb1437/image.png" alt=""></p>
<p>다른 부분은 풀 수 있었는데 처음에 <strong>가장 오래된 3마리</strong>를 어떻게 출력해야할까?였다. 우선은 <code>DATETIME</code>으로 정렬하는것까지는 알았는데 딱 3마리 출력하는걸 모르겠어서 찾아봤다.</p>
<p><code>LIMIT</code>을 사용하면 출력할 행의 수를 제한할 수 있었다. 이걸 사용해서 앞에 짜두었던 SQL문에서 제한을 3줄만 출력하도록 제한해 문제를 풀었다.</p>
<p>입양되지 않은 동물을 찾는 것은 전에는 서브쿼리 사용했었는데, 이번에는 입양된 동물들 테이블 기준 외부 조인을 사용해서 합친 테이블에서 <code>ANIMAL_OUT</code> 테이블의 속성이 널인 것을 이용해서 <code>WHERE</code> 문을 통해 구했다.</p>
<hr>
<blockquote>
<h3 id="q3">Q3.</h3>
</blockquote>
<h4 id="사이버캠퍼스-문제-입양시각-구하기">사이버캠퍼스 문제: 입양시각 구하기</h4>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/f0af7dc1-6ae7-43d8-9127-f97d491c3664/image.png" alt=""></p>
<p><code>DATETIME()</code>은 <code>연-월-일 시:분:초</code>를 모두 갖는데, 이런 데이터를 <strong><code>HOUR()</code></strong> 함수에 넣게되면 시간만을 얻을 수 있다.</p>
<p>조건에 따라 테이블에 시간을 출력하기 위해 <code>HOUR(DATETIME)</code>을 통해 시간만 얻어낸 것을 출력명을 지정해주고, 해당 속성명을 사용해 아래에서도 그룹화를 진행했다. <code>WHERE</code>절의 조건에서는 SELECT에서 사용한 별칭을 사용할 수 없기에 따로 함수를 사용했다.</p>
<p>문제 풀다가 기왕 정리하는김에 날짜 데이터 다루는 것과 관련된 내용 찾을 겸 정리를 했다.</p>
<blockquote>
<h4 id="날짜-데이터타입">날짜 데이터타입</h4>
<p><strong>DATE</strong>: 날짜 정보를 갖는 타입. <strong>&#39;YYYY-MM-DD&#39;</strong> 형식 사용
<strong>TIME</strong>: 시간 정보를 갖는 타입. <strong>&#39;YYYY-MM-DD&#39;</strong> 형식 사용
<strong>DATETIME</strong>: 날짜와 시간 정보를 모두 갖는 타입. <strong>&#39;YYYY-MM-DD YYYY-MM-DD&#39;</strong> 형식 사용 <br> </p>
</blockquote>
<h4 id="사용">사용</h4>
<p>SELECT나 WHERE 절에서 <strong><code>YEAR()</code>, <code>MONTH()</code>, <code>DAY()</code>, <code>HOUR()</code>, <code>MINUTE()</code>, <code>SECOND()</code></strong> 를 통해 사용하면 각각 연/월/일/시/분/초로 출력 형식을 정하거나 조건을 확인할 수 있다.</p>
<p>정리하다가 궁금해진거는 <code>SELECT</code>에서 설정한 별칭을 왜 <code>GROUP BY</code>에서 사용할 수 있을까였다. <code>ORDER BY</code>는 <code>SELECT</code> 이후에 실행되니까 그렇다고쳐도, <code>GROUP BY</code>는 이전에 실행되니까...?</p>
<p>=&gt; 찾아봤을 때는 실행되는 순서는 저게 맞고, 실제로는 사용이 안되지만? MySQL 같은 현대적인 DB 엔진에서 저런 부분을 허용해주는 경우가 있다고 한다. 오라클 같은 데에서는 못 쓸 수도 있다고...? </p>
<p>위 상황을 생각해서 아래처럼 수정을 했다. WHERE 절에서 사용한 것처럼 <code>HOUR()</code>안에 넣어주었다.
<img src="https://velog.velcdn.com/images/summeryoung_/post/1d35cf98-b771-4db4-801f-4ff27b1c6da1/image.png" alt=""></p>
<hr>
<blockquote>
<h3 id="q4">Q4.</h3>
</blockquote>
<h4 id="null-처리하기">NULL 처리하기</h4>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/abdcf5f4-be18-4d2c-ac66-d27d68653893/image.png" alt=""></p>
<p>앞의 다른 문제랑 비슷하게 조건은 처리할 수 있었다. NULL 값을 처리하는 방법을 몰랐었는데 클래스에 있는 문제 풀면서 익혔던 부분이라 <code>IFNULL</code>을 사용해서 처리했다.</p>
<blockquote>
<h4 id="정리">정리</h4>
<p><strong><code>IFNULL()</code></strong>: 해당 컬럼의 값이 널일 때, 다른 값을 출력하도록함</p>
</blockquote>
<pre><code class="language-sql">SELECT IFNULL(컬럼명,&quot;NULL일 때 대체값)
FROM ...</code></pre>
<p><br><strong><code>COALESCE()</code></strong>: 지정한 표현식들 중 널값이 아닌 첫 번째 값을 반환</p>
<pre><code class="language-sql">SELECT COALESCE(컬럼명1, 컬럼명2,....)
SELECT COALESCE(컬럼명1, &quot;컬러명1이 널값일 때 대체값&quot;)</code></pre>
<p><code>COALESCE</code> 써서도 아래처럼 풀었는데 똑같은 형식으로 사용하면 됐다.
<img src="https://velog.velcdn.com/images/summeryoung_/post/afb25bfd-a9e4-4c1d-adee-7da89b52077e/image.png" alt=""></p>
<hr>
<blockquote>
<h3 id="q5">Q5.</h3>
</blockquote>
<h4 id="datetime에서-date로-형-변환">DATETIME에서 DATE로 형 변환</h4>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/430c03e4-7b0a-4d42-9e4e-f63b9d44bec1/image.png" alt=""></p>
<p><code>DATETIME</code>의 출력 형식을 바꾸는 것 중에 <code>DATE_FORMAT</code>을 사용해서 풀 수 있는 문제였다. 다른 부분은 비슷하고, <code>SELECT</code>에서 포맷만 설정해주었다.</p>
<blockquote>
<h4 id="date_format-정리">DATE_FORMAT 정리</h4>
<p><code>DATE_FORMAT(날짜, 형식)</code>과 같은 식으로 넣어서 사용해준다. 형식을 지정하는 기호들은 아래와 같은 것들이 있다.
<code>%Y</code>: 4자리 년도, <code>%y</code>: 2자리 년도
<code>%m</code>: 숫자 월, <code>%d</code>: 일자
<code>%H-%i-%S</code>: 시(24시간)-분-초
<code>%I</code>: 시간(12시간)
<code>%T</code>: hh:mm:SS</p>
</blockquote>
<hr>
<blockquote>
<h3 id="q6">Q6.</h3>
</blockquote>
<h4 id="중성화-여부-파악하기">중성화 여부 파악하기</h4>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/61ef3b10-7a8f-4872-abd2-8958e98ae71f/image.png" alt=""></p>
<p><code>CASE WHEN</code>을 사용해서 풀이한 문제였다. <code>CASE WHEN</code>을 사용할 줄 몰라서 일단 찾아봤는데 찾았는데도 계속 오류가 나서 뭐가 문제일까 고민했다.</p>
<p>나중에 다른 방식을 사용해서 풀고 찾아보니 일전에는 계속 </p>
<pre><code class="language-sql">CASE SEX_UPON_INTAKE
    WHEN LIKE &#39;Neutered%&#39;...</code></pre>
<p>이런식으로 썼었는데 <code>LIKE</code>을 사용할 때는 <code>CASE</code> 컬럼(단순형)... 형식을 쓰면 안되고 검색형을 사용해야한다고 한다. (검색형은 <code>CASE</code> 뒤에 컬럼을 바로 쓰는게 아니라, <code>WHEN</code> 뒤에 조건식을 쓰는 형식이다.</p>
<pre><code class="language-sql">CASE &quot;여기는 비우고&quot;
    WHEN SEX_UPON_INTAKE LIKE &#39;Neutered%&#39;...</code></pre>
<p>이렇게 조건식을 쓰기.</p>
<blockquote>
<h4 id="case-when">CASE WHEN</h4>
<p>SELECT (<strong>CASE</strong> (컬럼명/값)
    <strong>WHEN</strong> (값: 단, 단순형에서는 무조건 = 비교만 가능) <strong>THEN</strong>
    WHEN ... THEN
    <strong>END</strong>) AS (새로운 컬럼명)
<br>
SELECT (<strong>CASE</strong>
    <strong>WHEN</strong> (조건식) <strong>THEN</strong>
    ...
    <strong>END</strong>) AS (새로운 컬럼명)</p>
</blockquote>
<hr>
<blockquote>
<h3 id="q7">Q7.</h3>
</blockquote>
<h4 id="solvesql-우유와-요거트가-담긴-장바구니">solvesql: 우유와 요거트가 담긴 장바구니</h4>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/8797f11b-85f7-4e2b-bf9d-8ca9ba892ff1/image.png" alt=""></p>
<p>일단은 문제 보고 바로 생각난 풀이는 진짜 비효율적인? 그런데 제일 쉬운 그런 풀이였다. 맞긴 맞았는데 이것보다 좋은 방법이 있는 것 같아서 고민하다가 찾아봤다.</p>
<p><code>GROUP BY</code>하고 <code>HAVING</code>을 사용하면 더 효율적으로 풀 수 있을 것 같긴했는데 어떤 식으로 사용해야할 지 잘 감이 안왔는데, <code>WHERE</code>절에서 먼지 일단 우유와 요거트가 든 행만 남긴다 -&gt; 그리고 <code>GROUP BY</code>를 통해 ID 별로 묶고, <code>HAVING</code> 절에서 개수가 2 이상인 것(우유랑 요거트 행만 남겼으니까 2이상이면 무조건 이 둘이다 단, <code>COUNT(DISTINCT)</code>를 사용해서 우유가 2개 이런 경우는 제외시켜준다.)</p>
<p>처음에 <code>GROUP BY</code>하고 <code>HAVING</code>을 써야할 것 같았는데, 이러면 다른 상품의 개수도 포함된다고 생각하고 그냥 직관적으로 바로 생각나는걸로 풀었는데 진짜 <code>WHERE</code>에서 조건을 사용해서 한 번 걸러주면 되는데 이 생각을 못했다. 그리고 <code>DISTINCT</code> 사용해서 또 걸러내는 것도 생각을 못했었다. </p>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/8de52732-5831-44db-8299-625e008b7b0c/image.png" alt=""></p>
<p>위의 방법대로 푼 방식은 위와 같다. 훨씬 효율적이고 쿼리 길이도 짧다.</p>
<hr>
<blockquote>
<p><strong>후기?</strong>
문제 풀다가 중간쯤에 깨달은 건데? 이거 소문자로 써도 괜찮았는데 왜 계속 대문자로 썼지 싶다(컬럼명). 아무생각없이 문제가 대문자라 계속 대문자로 썼는데 소문자로 써도 돌아갔다 생각해보니.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터베이스 4주차 내용 정리]]></title>
            <link>https://velog.io/@summeryoung_/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-4%EC%A3%BC%EC%B0%A8-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@summeryoung_/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-4%EC%A3%BC%EC%B0%A8-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Fri, 27 Mar 2026 07:07:12 GMT</pubDate>
            <description><![CDATA[<h1 id="데이터-조작어-dml">데이터 조작어 (DML)</h1>
<h2 id="부속질의">부속질의</h2>
<p>: SELECT문 안에 <strong>또 다른 SELECT문을 포함</strong>하는 질의</p>
<h3 id="부속-질의문서브-쿼리">부속 질의문(서브 쿼리)</h3>
<p>: 다른 SELECT문 안에 들어 있는 SELECT문</p>
<ul>
<li><p>괄호로 묶어서 작성하며, <strong>ORDER BY</strong>를 사용할 수 없음</p>
</li>
<li><p>하나의 행을 결과로 반환하기도 하고, 하나 이상의 행을 결과로 반환하기도함.</p>
</li>
<li><p>부속 질의문을 먼저 수행하고 그 결과를 바탕으로 상위 질의 문 수행</p>
</li>
<li><p>부속 질의문과 상위 질의문을 <strong>연결하는 연산자</strong> 필요</p>
<ul>
<li>단일 행의 경우 <strong>비교 연산자</strong> 사용이 가능하지만, 다중 행의 경우는 불가능</li>
</ul>
</li>
</ul>
<br>

<h3 id="연결-연산자">연결 연산자</h3>
<ol>
<li>비교 연산자: <code>=</code>, <code>&gt;</code>, <code>&lt;</code>, <code>&gt;=</code>, <code>&lt;=</code> (단일 행 부속 질의문의 경우 사용 가능)</li>
<li><code>IN</code>, <code>NOT IN</code>: 부속 질의문의 <strong>결과값</strong> 중 <strong>일치하는 것의 존재 여부</strong>에 따라 검색조건 참/거짓 반환.</li>
<li><code>EXISTS</code>, <code>NOT EXISTS</code>: 부속 질의문의 <strong>결과 값이 하나라도 존재하는지의 여부</strong>에 따라 검색조건 참/거짓 반환</li>
<li><code>ALL</code>: 부속 질의문읠 결과값 모두와 비교해 결과가 참일 때</li>
<li><code>ANY</code> 또는 <code>SOME</code>: 부속 질의문의 결과값 중 하나라도 비교해 결과가 참일 때.
 (<code>ALL</code>, <code>ANY</code>, <code>SOME</code>은 비교연산자와 함께 사용)</li>
</ol>
<br>

<p>예) 대한미디어에서 출판한 도서를 구매한 고객의 이름 나타내기</p>
<pre><code class="language-sql">SELECT name
FROM customers
WHERE custid IN ( 
        SELECT custid
        FROM orders
        WHERE bookid IN ( 
              SELECT bookid
              FROM books
              WHERE publisher = &#39;대한미디어&#39;)));</code></pre>
<p>서브쿼리를 사용해도 좋지만 <code>JOIN</code>을 사용하여서도 같은 결과를 낼 수 있다.
아래는 <code>(INNER) JOIN</code>을 사용해 같은 </p>
<pre><code class="language-sql">SELECT C.name
FROM customers AS C
    JOIN orders AS O ON C.custid = O.custid) 
    JOIN books AS B ON O.bookid = B.bookid
WHERE B.publisher = &#39;대한미디어&#39;;
</code></pre>
<h3 id="상관연결-부속질의">상관(연결) 부속질의</h3>
<ul>
<li>부속 질의 간에는 <strong>상하 관계</strong>가 있으며, 상위 부속질의와 하위 부속질의가 독립적이지 않고 <strong>서로 관련을 맺고 있음</strong></li>
<li>상위 쿼리와 하위 쿼리는 <strong>서로 의존적</strong>이며 상위 쿼리의 특정 행 값이 하위 쿼리 조건에 사용됨</li>
<li>일반적인 부속질의와 달리 <strong>행 단위로 반복 실행</strong>됨</li>
</ul>
<p>예) 출판사별로 출판사의 평균 도서 가격보다 비싼 도서 구하기</p>
<pre><code class="language-sql">SELECT bookname
FROM books B1
WHERE B1.price &gt; (
        SELECT avg(price)
        FROM books B2
        WHERE B1.publisher = B2.publisher));</code></pre>
<p>위의 코드를 살펴보면 같은 테이블인 books를 <code>B1</code>, <code>B2</code>로 별칭을 각각 설정해주고 있는데 이는 <strong>상위 쿼리와 하위 쿼리가 독립적이지 않아서</strong>이다. </p>
<p>상위 쿼리에서 행을 하나씩 가져와 하위 쿼리에서 반복하며 상위 쿼리 행의 책의 출판사의 책들의 값의 평균을 구해주면 그것과 상위 쿼리 행의 값을 비교하는 식으로 돌아가기에, 하위 쿼리에서 상위 쿼리의 내용을 가져와야하는데 이때 <strong>구분</strong>을 위해서 필요하다. </p>
<p><span style="color:gray; font-size:80%"> 반복문이 아닌데 반복문을 실행하는 것처럼 상위 쿼리의 한 행마다 이제 하위 쿼리가 실행된다는 부분이 처음에는 잘 받아들여지지 않았는데 직접 쳐보니 조금 더 와닿는다.</span></p>
<p>위의 코드도 <code>JOIN</code>을 쓰는 식으로 수정해보면 아래와같다.</p>
<pre><code class="language-sql">SELECT bookname
FROM books B1
    JOIN (SELECT publisher, AVG(price) AS price
          FROM books
          GROUP BY publisher) B2
    ON B1.publisher = B2.publisher
WHERE B1.price &gt; B2.price;</code></pre>
<h3 id="집합-연산-union-minus-intersect">집합 연산: UNION, MINUS, INTERSECT</h3>
<ul>
<li>SQL문의 결과는 테이블로 나타남</li>
<li>테이블 간의 집합 연산을 사용해 합집합, 차집합, 교집합을 구할 수 있음</li>
</ul>
<h4 id="union">UNION</h4>
<ul>
<li><code>UNION ALL</code>은 <strong>중복을 포함</strong>하여 모든 결과를 구함</li>
</ul>
<p>예) Customer 테이블에 대한민국에 거주하는 고객의 이름 집합과 도서를 주문한 고객의 이름 집합의 합집합 구하기</p>
<pre><code class="language-sql">SELECT name
FROM customers
WHERE address LIKE &#39;%대한민국%&#39;
UNION
SELECT customers.name
FROM customers
    JOIN orders 
    ON customers.custid = orders.custid;</code></pre>
<h4 id="minus-interset">MINUS, INTERSET</h4>
<ul>
<li>MySQL에는 MINUS와 INTERSECT 연산자가 없어 <strong>NOT IN</strong>과 <strong>IN</strong> 연산자를 사용함</li>
</ul>
<p>예) 대한민국에 거주하는 고객의 이름에서 도서를 주문한 고객의 이름을 제외하고 나타내기</p>
<pre><code class="language-sql">SELECT C.name
FROM customers AS C
WHERE C.address LIKE &#39;%대한민국%&#39; AND    
      C.custid NOT IN (
              SELECT custid
            FROM orders);</code></pre>
<h4 id="exist-not-exist">EXIST, NOT EXIST</h4>
<ul>
<li><strong>상관 부속질의문</strong>의 형식으로 부속질의의 결과가 존재하는지 여부를 확인하는 연산자</li>
<li>부속질의문이 한 행이라도 반환하면 참, NOT EXISTS의 경우는 반환행이 하나도 존재하지 않으면 참</li>
</ul>
<p>예) 주문이 있는 고객의 이름과 주소를 나타내기</p>
<pre><code class="language-sql">SELECT name, address
FROM customers C
WHERE EXIST (
        SELECT custid
        FROM orders O
        ON C.custid = O.custid));</code></pre>
<hr>
<h1 id="데이터-정의어-ddl">데이터 정의어 (DDL)</h1>
<h2 id="create">CREATE</h2>
<h3 id="create-table">CREATE TABLE</h3>
<p>테이블을 생성하며, <strong>속성과 속성에 관한 제약</strong>을 정의한다.
<strong>기본키 및 외래키를 정의</strong>하는 명령어이다.</p>
<blockquote>
<p><strong>CREATE TABLE</strong> 테이블 이름 (
    &lt;<em>속성명</em>&gt; &lt;<em>데이터 타입</em>&gt; &lt;<em>제약...</em>&gt;
    <strong>PRIMARY KEY</strong>(<em>속성명</em> )
    <strong>FOREIGN KEY</strong> (<em>속성명</em> )
);</p>
</blockquote>
<br>

<h3 id="외래키-지정">외래키 지정</h3>
<ul>
<li><strong>참조 무결성 제약조건</strong> 유지를 위해 참조되는 테이블에서 튜플 삭제 시 처리방법을 지정하는 옵션<blockquote>
<p><strong>ON DELETE CASCADE</strong>: 관련 튜플을 함께 삭제</p>
</blockquote>
</li>
<li><em>ON DELETE SET NULL*</em>: 관련 튜플의 외래키 값을 NULL로 변경</li>
<li><em>ON DELETE RESTRICT*</em>: 부모행이 참조되고 있으면 삭제/수정 불가 (기본값)</li>
<li><em>ON DELETE NO ACTION*</em>: SQL 표준 키워드로 RESTRICT와 동일.</li>
</ul>
<p>예)</p>
<pre><code class="language-sql">CREATE TABLE post (
    post_id INT NOT NULL AUTO_INCREMENT,
    board_id INT NOT NULL,
    content TEXT,
    createdAt DATETIME NOT NULL,
    PRIMARY KEY(post_id),
    FOREIGN KEY(board_id) REFERENCES board ON DELETE CASCADE
);</code></pre>
<h2 id="alter">ALTER</h2>
<p>: 생성된 테이블의 <strong>속성 변경 및 속성에 관한 제약 변경</strong> 또는 <strong>기본키 및 외래키</strong>를 변경</p>
<blockquote>
<p><strong>ALTER TABLE</strong> &lt;<em>테이블명</em>&gt;
<strong>ADD</strong> &lt;<em>속성이름</em>&gt; &lt;<em>데이터타입</em>&gt;
<strong>DROP COLUMN</strong> &lt;<em>속성이름</em>&gt;
<strong>ALTER COLUMN</strong> &lt;<em>속성이름</em>&gt; &lt;<em>데이터타입</em>&gt;
<strong>ADD PRIMARY KEY</strong>(<em>속성이름</em> )</p>
</blockquote>
<pre><code class="language-sql">ALTER TABLE post
    ADD updatedAt DATETIME
       DROP COLUMN createdAt;</code></pre>
<h2 id="drop">DROP</h2>
<p>: <strong>테이블을 삭제</strong>하는 명령. 테이블의 구조와 데이터를 모두 삭제함.</p>
<p>데이터만 삭제하려고 할 때는 <strong>DELETE</strong>를 사용하며, 삭제할 테이블을 <strong>참조하는 테이블</strong>이 있을 때에는 삭제가 수행되지 않음.</p>
<p>위의 경우 </p>
<ul>
<li>해당 테이블을 참조하고 있는 테이블부터 삭제</li>
<li>관련된 외래키 제약조건 먼저 삭제</li>
</ul>
<p>를 통해 테이블을 삭제할 수 있다.</p>
<blockquote>
<p><strong>DROP TABLE</strong> &lt;<em>테이블 이름</em>&gt;</p>
</blockquote>
<hr>
<h1 id="데이터-조작어dml">데이터 조작어(DML)</h1>
<h2 id="insert">INSERT</h2>
<p>: 테이블에 새로운 튜플을 삽입하는 명령어</p>
<blockquote>
<p><strong>INSERT INTO</strong> &lt;<em>테이블 이름</em>&gt; (<em>속성 리스트</em> )
    <strong>VALUES</strong> (<em>값 리스트</em> )<br>
    <span style=font-size:80%>속성 리스트를 작성한 순서대로(<strong>일대일대응</strong>) 값 리스트를 작성해줘야함
    속성리스트는 생략 가능하지만, 테이블 정의 시 지정한 속성의 순서대로 값이 삽입되기에 주의해야함. </span></p>
</blockquote>
<p><code>SELECT</code> 문을 사용해서도 작성할 수 있는데 이 경우는 <strong>다른 테이블의 값을 긁어와 넣는 식</strong>.</p>
<p>예)</p>
<pre><code class="language-sql">INSERT INTO post (post_id, board_id, content, createdAt)
VALUES (1, 4, &#39;글입니다.&#39;, &#39;2026-03-27 16:00:10&#39;);

---생략한 속성들에는 NULL 또는 DEFAULT로 설정한 값 입력.
INSERT INTO post (post_id, board_id)
VALUES (5, 3);</code></pre>
<h2 id="update">UPDATE</h2>
<p>: 특정 속성값을 수정하는 명령으로 다른 테이블의 속성값을 이용할 수 있음.</p>
<blockquote>
<p><strong>UPDATE</strong> &lt;<em>테이블 이름</em>&gt;
<strong>SET</strong> <em>속성이름</em> = <em>값</em>
<strong>WHERE</strong> <em>조건</em> <br>
<span style=font-size:80%> WHERE 절은 생략 가능하지만, 이 경우 테이블에 존재하는 모든 튜플을 수정하게된다. </span></p>
</blockquote>
<p>예)</p>
<pre><code class="language-sql">UPDATE post
SET content = &quot;ringing siren&quot;
WHERE post_id = 5;</code></pre>
<h2 id="delete">DELETE</h2>
<p>: 테이블에 있는 기존 튜플을 삭제하는 명령</p>
<blockquote>
<p><strong>DELETE FROM</strong> &lt;<em>테이블 이름</em>&gt;
<strong>WHERE</strong> <em>조건</em>
<br><span style=font-size:80%> UPDATE문과 동일하게 WHERE 절은 생략 가능하지만, 이 경우에는 테이블에 존재하는 모든 튜플을 삭제하게 된다. </span></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[인프런 데이터베이스] 3주차 스터디]]></title>
            <link>https://velog.io/@summeryoung_/%EC%9D%B8%ED%94%84%EB%9F%B0-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-3%EC%A3%BC%EC%B0%A8-%EC%8A%A4%ED%84%B0%EB%94%94</link>
            <guid>https://velog.io/@summeryoung_/%EC%9D%B8%ED%94%84%EB%9F%B0-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-3%EC%A3%BC%EC%B0%A8-%EC%8A%A4%ED%84%B0%EB%94%94</guid>
            <pubDate>Mon, 23 Mar 2026 13:22:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="인프런-데이터베이스-강의-섹션6-섹션7-기록">인프런 데이터베이스 강의 (섹션6-섹션7) 기록</h3>
</blockquote>
<h2 id="관계형-데이터베이스">관계형 데이터베이스</h2>
<p>두 개의 테이블에 분산해서 저장하고 읽어와 출력에는 이를 합쳐서 보여줄 수 있음. 
중복을 제거할 수 있다는 것이 장점.</p>
<p><code>RENAME TABLE</code>: 테이블 이름 수정 가능</p>
<p>author라는 테이블을 기존의 topic 테이블에서 따로 분리해 만들어 중복하여 저장하는 author의 프로필이나 이름을 생략. 대신 <code>author_id</code>를 <code>topic</code> 테이블에 <code>author</code> 대신 넣어줌.</p>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/67370292-0c6d-40b0-a8b4-24ffa6e0e2df/image.png" width=70%>)</p>
<p>아래와 같은 형태로 출력됨.</p>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/1a4c2243-a705-403c-b231-36266b1777ee/image.png" width=70%>)</p>
<h3 id="조인join">조인(JOIN)</h3>
<p>두 개의 분리된 테이블을 하나의 형태로 합쳐서 출력할 수 있음. 
위의 테이블에서 연결 고리는 <code>author_id</code>라고 할 수 있음.</p>
<pre><code class="language-sql">SELECT * FROM topic LEFT JOIN author ON topic.author_id = author.id;</code></pre>
<p>topic의 author_id와 author 테이블의 id값이 같은 것을 기준으로 두 테이블을 합쳐서 출력함.</p>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/6ce289e8-03e2-4175-8b2d-188dab61b8ad/image.png" alt=""></p>
<p>author의 아이디나, author 아이디를 생략하려면 <code>SELECT</code> 문의 뒤에 보여주고 싶은 속성명만 적어준다.</p>
<pre><code class="language-sql">SELECT topic.id, title, description, created, name, profile  FROM topic LEFT JOIN author ON topic.author_id = author
.id;</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/7c3dcc37-36f0-4fbe-890b-4bd34f0755fb/image.png" alt="">
하지만 위의 예시처럼 <code>id</code>를 선택할 경우 해당 <code>id</code>가 topic 테이블의 속성을 말하는지, author 테이블의 속성을 말하는지 모르기 때문에 <strong>&lt;테이블명&gt;.&lt;속성명&gt;</strong>의 형식으로 확실하게 명시하여준다.</p>
<p>또, 출력되는 표의 속성명을 다르게 수정하고 싶을 때는 <code>AS</code>를 사용해 별칭을 만들어줄 수 있다.</p>
<pre><code class="language-sql">SELECT topic.id AS topic_id, title, description, created, name, profile  FROM topic LEFT JOIN author ON topic.author
_id = author.id;</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/7b0c87ac-d58f-4547-92f4-04dfaa31ae72/image.png" alt=""></p>
<p>이렇게 테이블을 분리하게되면, <strong>수정 및 유지 보수에서 이점을</strong> 얻을 수 있음.</p>
<h2 id="인터넷과-데이터베이스의-관계">인터넷과 데이터베이스의 관계</h2>
<h3 id="데이터베이스-서버">데이터베이스 서버</h3>
<p>MySQL을 설치하면 데이터베이스 클라이언트와 데이터베이스 서버 2가지를 설치하게됨. 데이터베이스 서버에 실제로 데이터가 저장되며 데이터베이스 클라이언트 쪽에서 이 데이터베이스 서버에 접속할 수 있음.</p>
<h3 id="데이터베이스-클라이언트">데이터베이스 클라이언트</h3>
<p>데이터베이스 서버는 직접 다룰 수 없고 <strong>반드시 데이터베이스 클라이언트</strong>를 사용해야함. 터미널에서 지금껏 사용하던 것은 CLI를 사용해 접근하는 데이터베이스 클라이언트 중 하나인 <code>mysql-monitor</code> 였음.</p>
<p><code>mysql-monitor</code>의 장점(CLI):</p>
<ul>
<li>명령어를 사용해서 제어</li>
<li>mysql을 설치하면 같이 설치되며 어디에서나 실행할 수 있음.</li>
</ul>
<p>MySQL Workbench는 GUI 형식의 데이터베이스 클라이언트.</p>
<pre><code class="language-bash">./mysql -uroot -p -h localhost</code></pre>
<p>-<code>-h</code>: 접속하려는 데이터베이스 서버의 주소를 적어주는 부분</p>
<ul>
<li><code>localhost</code> = <code>127.0.0.1</code>: 스스로의 컴퓨터를 가리키는 뜻.</li>
</ul>
<h3 id="인터넷">인터넷</h3>
<p>동작을 위해서는 최소 2대의 컴퓨터가 필요함. 인터넷은 컴퓨터들이 모여 이루는 사회라고 볼 수 있음.</p>
<p>한 대의 컴퓨터는 다른 컴퓨터에게 정보를 <strong>요청</strong>하고, 다른 컴퓨터는 정보를 <strong>응답</strong>함. 요청하는 쪽을 <strong>클라이언트</strong>, 응답하는 쪽은 <strong>서버</strong>라고 함.</p>
<p>웹에 비유를 하면 웹 브라우저(웹 클라이언트)가 웹 서버 측에 요청을 보내게됨.</p>
<h2 id="mysql-workbench">MySQL Workbench</h2>
<p>SQL문을 생성해서 서버로 전달하는 것이 모든 서버 클라이언트들의 동작.</p>
<p>아래처럼 GUI를 사용해 스키마를 새로 만들 수 있는데 이  역시도 결국 SQL문이 만들어지고 실행되는 형식.</p>
<img src="https://velog.velcdn.com/images/summeryoung_/post/6b2fadb4-2886-49db-a887-d9b550dfc5ac/image.png" width=70%>

<img src="https://velog.velcdn.com/images/summeryoung_/post/9413ee77-be2d-495d-9042-363c6efedfe8/image.png" width=70%>


<p>아래 처럼 테이블 생성 시 속성들을 GUI를 사용해 설정할 수 있게된다.</p>
<img src="https://velog.velcdn.com/images/summeryoung_/post/6355feb5-f782-44c4-b7a7-cfd5083bf34a/image.png" width=70%>

<img src="https://velog.velcdn.com/images/summeryoung_/post/31b429c9-de80-48a6-8e91-e7fb0b8d77ab/image.png" width=70%>

<h2 id="추가">추가</h2>
<p>정보가 많아지며 생기는 문제점들이 존재함. 데이터가 많아질수록 무언가를 찾을 때 시간이 오래 걸리는 문제들이 존재함.</p>
<h3 id="index">index</h3>
<p>이를 해결하기 위해 사용자들이 자주 검색하는 <strong>컬럼(속성)에 색인(index)</strong>을 걸어줌. 이렇게 색인을 걸 경우, 데이터가 들어올 때 데이터베이스가 해당 컬럼의 데이터를 잘 정렬해 정리해둠. 후에 요청 시 빠르게 응답할 수 있음.</p>
<h3 id="modeling">modeling</h3>
<p>테이블을 효율적으로 잘 설계해야함. 정규화, 비정규화 등. 데이터가 많아지면서 이를 관리/설계해야할 필요성을 느낄 시 공부.</p>
<h3 id="backup">backup</h3>
<p>데이터가 날라가면 문제가 발생하기에 잘 백업해두어야함. 하드 디스크의 고장 등의 문제가 있을 수 있기에 정보를 안전하게 잘 보장해야함. <strong>데이터를 복제해서 보관</strong>하여 백업하는 것이 중요.</p>
<p>mysqldump나 binary log를 찾아보기</p>
<h3 id="cloud">cloud</h3>
<p>컴퓨터를 데이터베이스 서버로 쓰지 않고, 거대한 회사가 운영하는 인프라 위의 컴퓨터를 임대해서 사용하는 것이 <strong>클라우드 컴퓨팅</strong>. 이는 <strong>원격 제어</strong>를 통해 먼 곳에 있는 컴퓨터를 제어. 백업 등을 알아서 관리해주기에 편리함.</p>
<p>예) AWS, Google Cloud, AZURE 등</p>
<hr>
<h2 id="3주차-추가-과제">3주차 추가 과제</h2>
<h3 id="테이블-생성">테이블 생성</h3>
<p>passenger와 plane 두 개의 테이블을 생성.</p>
<pre><code class="language-sql">CREATE TABLE passenger (
    passenger_id INT(11) NOT NULL AUTO_INCREMENT,
    last_name VARCHAR(50) NOT NULL,
    first_name VARCHAR(50) NOT NULL,
    nationality VARCHAR(50) NOT NULL,
    plane INT(11) NULL,
    PRIMARY KEY(passenger_id)
);

CREATE TABLE plane(
    plane_id INT PRIMARY KEY AUTO_INCREMENT,
    departure VARCHAR(45) NOT NULL,
    arrival VARCHAR(45) NOT NULL,
    departure_time DATETIME NOT NULL,
    gate INT NOT NULL,
    meal BOOLEAN
);</code></pre>
<p><img src="
https://velog.velcdn.com/images/summeryoung_/post/e7101c0d-9442-42df-84a8-168338008e81/image.png" width=70%></p>
<img src="https://velog.velcdn.com/images/summeryoung_/post/8f78f7e6-973b-41ae-b786-e8cc31783605/image.png" width=70%>
<br>

<h3 id="데이터-삽입">데이터 삽입</h3>
<pre><code class="language-sql">INSERT INTO passenger (last_name, first_name, nationality, plane)
VALUES (&quot;김&quot;, &quot;퍼비&quot;, &quot;한국&quot;, 1);
INSERT INTO passenger (last_name, first_name, nationality, plane)
VALUES (&quot;Smith&quot;, &quot;Oliver&quot;, &quot;호주&quot;, 4);
...

INSERT INTO plane (departure, arrival, departure_time, gate, meal)
VALUES (&quot;서울&quot;, &quot;로마&quot;, &quot;2023-03-30 12:10:00&quot;, 57, true);
INSERT INTO plane (departure, arrival, departure_time, gate, meal)
VALUES (&quot;서울&quot;, &quot;오사카&quot;, &quot;2023-04-14 09:35:00&quot;, 9, false);
...</code></pre>
<img src="https://velog.velcdn.com/images/summeryoung_/post/2913eab4-c144-4120-964d-5da92862319e/image.png" width=70%>

<p><img src="https://velog.velcdn.com/images/summeryoung_/post/81cff682-28fb-4980-905d-1588353befd5/image.png" alt=""></p>
<h3 id="조인join-활용-쿼리-조회">조인(JOIN) 활용 쿼리 조회</h3>
<pre><code class="language-sql">SELECT passenger_id, last_name, first_name, nationality, departure, arrival,
departure_time, gate, meal
FROM passenger 
    LEFT JOIN plane
    ON passenger.plane = plane.plane_id;</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/9b652a83-5f3d-4036-9f1d-08dc3211b26c/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[자바 ORM 표준 JPA 프로그래밍] 2주차 스터디]]></title>
            <link>https://velog.io/@summeryoung_/%EC%9E%90%EB%B0%94-ORM-%ED%91%9C%EC%A4%80-JPA-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-2%EC%A3%BC%EC%B0%A8-%EC%8A%A4%ED%84%B0%EB%94%94</link>
            <guid>https://velog.io/@summeryoung_/%EC%9E%90%EB%B0%94-ORM-%ED%91%9C%EC%A4%80-JPA-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-2%EC%A3%BC%EC%B0%A8-%EC%8A%A4%ED%84%B0%EB%94%94</guid>
            <pubDate>Sun, 22 Mar 2026 06:47:58 GMT</pubDate>
            <description><![CDATA[<h1 id="3장-영속성-관리">3장. 영속성 관리</h1>
<blockquote>
<p>JPA가 제공하는 기능</p>
</blockquote>
<ol>
<li><strong>엔티티와 테이블을 매핑</strong>하는 설계 부분</li>
<li><strong>매핑한 엔티티를 실제 사용</strong>하는 부분</li>
</ol>
<p><strong>엔티티 매니저(Entity Manager)</strong>: 엔티티를 저장/수정/삭제/조회하는 등 엔티티와 관련된 모든 일을 처리.</p>
<h2 id="31-엔티티-매니저-팩토리와-엔티티-매니저">3.1 엔티티 매니저 팩토리와 엔티티 매니저</h2>
<h3 id="엔티티-매니저-팩토리-entitymanagerfactory">엔티티 매니저 팩토리 (EntityManagerFactory)</h3>
<p>데이터베이스를 하나만 사용하는 경우 애플리케이션은 보통 <code>EntityManagerFactory</code>를 <strong>하나만</strong> 생성.</p>
<pre><code class="language-java">EntityManagerFactory emf = 
    Persistence.createEntityManagerFactory(&quot;jpabook&quot;);</code></pre>
<p><code>Persistence.createEntityManagerFactory</code> 를 호출할 경우 META-INF/<strong>persistence.xml</strong>의 정보를 바탕으로 엔티티 매니저 팩토리를 생성하게된다.</p>
<p><code>&quot;jpabook&quot;</code>의 부분에는 persistence.xml에 정의되어 있는 persistence-unit의 이름을 적어주면 된다.</p>
<h3 id="엔티티-매니저-entitymanager">엔티티 매니저 (EntityManager)</h3>
<p>엔티티 매니저 팩토리 생성 후에는 필요할 때마다 엔티티 매니저 팩토리에서 <strong>엔티티 매니저</strong>를 생성하면된다.</p>
<pre><code class="language-java">//엔티티 매니저 생성
EntityManager em = emf.createEntityManager();</code></pre>
<p>엔티티 매니저 팩토리는 만드는데에 비용이 큰 반면, 엔티티 매니저를 만드는 비용이 적다. 또, <strong>엔티티 매니저 팩토리는 여러 스레드가 동시에 접근해도 안전</strong>하지만, <strong>엔티티 매니저는 여러 스레드가 동시에 접근하면 동시성 문제가 발생하기에</strong> 스레드 간에 절대 공유하면 안된다.</p>
<p>엔티티 매니저는 데이터베이스의 연결이 꼭 필요한 시점까지는 커넥션을 얻지 않고, <strong>트랜잭션을 시작할 때 커넥션을 획득</strong>한다.</p>
<p>대부분의 JPA 구현체들은 엔티티 매니저 팩토리를 생성할 때 커넥션 풀을 만든다. 데이터베이스 접속 관련 정보는 persistence.xml에 존재한다. </p>
<hr>
<h2 id="32-영속성-컨텍스트">3.2 영속성 컨텍스트</h2>
<p><strong>영속성 컨텍스트(persistence context</strong>)란 엔티티를 영구저장하는 환경인데, 엔티티 매니저로 엔티티를 저장/조회하는 경우 <strong>엔티티는 영속성 컨텍스트에 엔티티를 보관하고 관리</strong>한다.</p>
<p><code>persist()</code> 메소드를 사용해 엔티티를 저장하는 것도 엔티티 매니저를 이용해 엔티티를 <strong>영속성 컨텍스트에 저장</strong>하는 것이다.</p>
<p>영속성 컨텍스트는 논리적인 개념으로 엔티티 매니저를 생성할 때 하나 만들어지며, 엔티티 매니저를 통해 접근하거나 관리할 수 있다.</p>
<hr>
<h2 id="33-엔티티의-생명주기">3.3 엔티티의 생명주기</h2>
<blockquote>
<p><strong>엔티티의 4가지 상태</strong></p>
</blockquote>
<ul>
<li>비영속: 영속성 컨텍스트와 전혀 관계가 없는 상태</li>
<li>영속: 영속성 컨텍스트에 저장된 상태</li>
<li>준영속: 영속성 컨텍스트에 저장되었다가 분리된 상태</li>
<li>삭제: 삭제된 상태</li>
</ul>
<h3 id="비영속">비영속</h3>
<p>순수한 객체상태로 아직 저장하지 않아 영속성 컨텍스트 및 데이터베이스와 모두 관련 이 없는 상태를 말함. 주로 객체 생성 직후 상태.</p>
<pre><code class="language-sql">Member m = new Member();
m.setId(&quot;mem1&quot;);
m.setUsername(&quot;회원1&quot;);
//아직 비영속 상태</code></pre>
<h3 id="영속">영속</h3>
<p>엔티티 매니저를 통해 <strong>엔티티를 영속성 컨텍스트에 저장</strong>한 상태로 <strong>영속성 컨텍스트가 관리</strong>하는 엔티티를 말함.</p>
<p><code>em.find()</code>나 JPQL을 사용해 조회하는 엔티티 모두 영속성 컨텍스트가 관리하는 영속 상태.</p>
<pre><code class="language-java">em.persist(m);</code></pre>
<h3 id="준영속">준영속</h3>
<p>영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 영속성 컨텍스트가 관리하지 않게된 상태를 말한다.</p>
<ul>
<li><code>em.detach()</code>: 엔티티를 준영속 상태로 전화</li>
<li><code>em.close()</code>: 영속성 컨텍스트 닫기. 엔티티들을 준영속 상태로 바꿈.</li>
<li><code>em.clear()</code>: 영속성 컨텍스트 초기화. 엔티티들을 준영속 상태로 바꿈.</li>
</ul>
<h3 id="삭제">삭제</h3>
<p>엔티티를 영속성 컨텍스트는 물론 데이터베이스에서도 삭제</p>
<pre><code class="language-java">em.remove(m);</code></pre>
<hr>
<h2 id="34-영속성-컨텍스트의-특징">3.4 영속성 컨텍스트의 특징</h2>
<h3 id="특징">특징</h3>
<ul>
<li><p>식별자와 값: 
영속성 컨텍스트는 엔티티를 식별자 값(<code>@Id</code>로 테이블의 기본키와 매핑한 값)으로 구분하기에 <strong>식별자값이 반드시 필요</strong>.</p>
<br></li>
<li><p>데이터베이스에의 저장: 
JPA는 보통 <strong>트랜잭션을 커밋하는 순간</strong> 영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영. 이를 플러시(flush)라 부름.</p>
<br></li>
<li><p>영속성 컨텍스트에서 엔티티 관리 시의 장점</p>
<ul>
<li>1차 캐시</li>
<li>동일성 보장</li>
<li>트랜잭션을 지원하는 쓰기 지연</li>
<li>변경 감지</li>
<li>지연 로딩</li>
</ul>
</li>
</ul>
<h3 id="엔티티-조회">엔티티 조회</h3>
<blockquote>
<p><strong>1차 캐시</strong>: 영속성 컨텍스트가 내부에 가지고 있는 캐시를 이르는 말. 영속 상태의 엔티티는 모두 이곳에 저장됨.</p>
</blockquote>
<pre><code class="language-java">//엔티티 생성(비영속 상태)
Member m = new Memeber();
m.setId(&quot;mem1&quot;);
m.setUsername(&quot;회원1&quot;);

//엔티티를 영속
em.persist(m);
</code></pre>
<p>위 코드에서는 회원 엔티티를 영속성 컨텍스트에 저장하긴 했지만, <strong>아직 데이터베이스에 저장X</strong></p>
<p>1차 캐시의 키는 <strong>식별자 값</strong>으로 데이터베이스의 기본키(primary key)와 매핑되어 있음. 즉, <strong>영속성 컨텍스트에 데이터를 저장/조회하는 기준</strong>은 모두 데이터베이스의 <strong>기본키값</strong>.</p>
<pre><code class="language-java">//엔티티 조회
Memeber mem = em.find(Memeber.class, &quot;mem1&quot;);</code></pre>
<p>엔티티 조회를 위한 메소드인 <code>find()</code>의 첫번째 파라미터는 <strong>엔티티의 클래스 타입</strong>이고, 두번째는 <strong>엔티티의 식별자값</strong>이다. <code>em.find()</code> 호출 시 엔티티를 <strong>1차 캐시에서 먼저</strong> 찾아보고, 없으면 <strong>데이터베이스에서 조회</strong>하는 식이다.</p>
<p>데이터베이스에서 조회할 때는, 1차 캐시에서 없는 엔티티이므로 데이터베이스 조회 후 <strong>엔티티를 생성, 1차캐시에 저장</strong>한 후에 <strong>영속 상태</strong>의 엔티티를 반환하는 식으로 동작한다.</p>
<blockquote>
<p><strong>영속 엔티티의 동일성 보장</strong></p>
</blockquote>
<pre><code class="language-java">Member a = em.find(Member.class, &quot;mem1&quot;);
Member b = em.find(Member.class, &quot;mem2&quot;);</code></pre>
<p>위의 코드에서 엔티티 인스턴스는 동일성을 가짐. (=즉, 실제 인스턴스가 같다.)</p>
<p>그 이유는 <code>em.find()</code>를 호출하게되면 영속성 컨텍스트가 <strong>1차 캐시에 있는 같은 엔티티 인스턴스를 반환</strong>하기 때문이다. 이는 성능상의 이점과 엔티티의 동일성을 보장해준다는 장점이 있다.</p>
<h3 id="엔티티-등록">엔티티 등록</h3>
<blockquote>
<p><strong>트랜잭션을 지원하는 쓰기 지연</strong></p>
</blockquote>
<pre><code class="language-java">EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경 시 트랜잭션을 시작해야함
transaction.begin();

...

transaction.commit();//트랜잭션 커밋</code></pre>
<p>엔티티 매니저는 트랜잭션을 커밋하기 직전까지 데이터베이스에 엔티티를 저장하지 않고 <strong>내부 쿼리 저장소</strong>에 INSERT SQL을 모아둔다. 이를 <strong>트랜잭션 커밋 시</strong> 데이터베이스에 한 번에 보냄으로 <strong>트랜잭션을 지원하는 쓰기 지연</strong>을 한다.</p>
<p>트랜잭션 커밋 시 엔티티 매니저는 우선 <strong>영속성 컨텍스트를 플러시(flush)</strong>함. </p>
<blockquote>
<p><strong>플러시(flush)</strong>: 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업으로 등록/수정/삭제한 엔티티를 데이터베이스에 반영. 즉, <strong>SQL 저장소의 쿼리들을 데이터베이스에 전송</strong>해준다.</p>
</blockquote>
<h3 id="엔티티-수정">엔티티 수정</h3>
<p><strong>SQL 수정 쿼리의 문제점</strong>:
SQL을 사용하게 되면 변경사항 등이 발생했을 때마다, 또 수정 사항이 속성 중 어떤 것이냐에 따라 등 다양한 수정 쿼리를 작성해야하고, 특정 요소의 수정이 빠지거나 했을 때 오류가 발생할 수 있는 오류가 존재하는 등의 문제가 있다.</p>
<p>수정 쿼리가 많아지고 비즈니스 로직 분석을 위해 SQL을 확인해야하며 비즈니스 로직이 SQL에 의존하게 된다는 문제 존재.</p>
<blockquote>
<p>** 변경 감지 **</p>
</blockquote>
<p>JPA를 통해 엔티티를 수정할 때에는 <strong>엔티티를 조회하고 데이터만 변경</strong>하면 된다. <code>em.update()</code> 같은 메소드는 없고, 변경사항이 데이터베이스에 자동으로 반영되는데 이 기능을 <strong>변경 감지</strong>라고 한다.</p>
<blockquote>
<p><strong>스냅샷</strong>: JPA가 영속성 컨텍스트에 엔티티를 보관할 때 <strong>최초 상태를 복사해 저장해두는 것</strong>을 말함.</p>
</blockquote>
<p>이 스냅샷과 플러시(flush) 시점의 엔티티를 비교해 변경된 엔티티를 찾을 수 있고, 변경사항이 있는 엔티티의 경우 수정 쿼리를 생성해 쓰기 지연 SQL 저장소에 전송하게된다.</p>
<p>이런 변경감지는 <strong>영속 상태</strong>의 엔티티에만 해당되며 준영속 상태의 경우에는 영속성 컨텍스트의 관리를 받지 않기에 값을 변경해도 데이터베이스에 반영되지 않는다.</p>
<h3 id="엔티티-삭제">엔티티 삭제</h3>
<p>엔티티 삭제를 위해서는 엔티티 조회가 우선되어야한다. 삭제 역시 즉시 데이터베이스에서 삭제하는 것이 아니라 <strong>쓰기 지연 SQL 저장소</strong>에 저장되었다가 <strong>트랜잭션 커밋 시 플러시가 호출되며 전달</strong>되는 식이다.</p>
<p><code>em.remove()</code> 호출 순간 엔티티는 영속성 컨텍스트에서 제거되니 재사용하지 않는 것이 좋다.</p>
<hr>
<h2 id="35-플러시flush">3.5 플러시(flush)</h2>
<p><strong>플러시(flush)</strong>는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 것을 말함. </p>
<blockquote>
<p><strong>플러시할 때 일어나는 일</strong></p>
</blockquote>
<ol>
<li>변경 감지 동작: 영속성 컨텍스트의 모든 엔티티를 <strong>스냅샷과 비교</strong>해 수정된 엔티티 탐색</li>
<li>쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송</li>
</ol>
<p>영속성 엔티티를 플러시하는 방법은 아래 3가지가 있음.</p>
<p><strong>1- <code>em.flush()</code> 직접 호출</strong>:
엔티티 매니저의 <code>flush()</code> 메소드를 호출해 영속성 컨텍스트를 강제 플러시. 다른 프레임워크와 JPA를 함께 사용하거나 테스트 외에는 잘 사용하지 않음.</p>
<p><strong>2-트랜잭션 커밋 시 플러시 자동 호출</strong>
데이터베이스에 변경내용을 SQL에 전달하지 않고 트랜잭션만 커밋하면 데이터가 데이터베이스에 반영되지 않음. 따라서 <strong>트랜잭션 커밋 전 플러시를 호출해 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영</strong>해야함. 이때문에 JPA에서는 트랜잭션 커밋 시 자동으로 플러시를 호출함.</p>
<p><strong>3-JPQL 쿼리 실행 시 플러시 자동 호출</strong>
JPQL은 SQL로 변환되어 데이터베이스에서 엔티티를 조회하는데 이때 앞서 변경된 영속성 컨텍스트가 데이터베이스에 반영되어 있지 않으면 결과가 잘못되기에 <strong>쿼리 실행 전 영속성 컨텍스트를 플러시</strong>해줘야함. 트랜잭션 커밋과 같은 이유로 JPQL 쿼리 실행 시에도 플러시가 자동 호출됨.</p>
<ul>
<li><code>FlushModeType.AUTO</code>: 커밋/쿼리 실행 시 자동 플러시(기본값)</li>
<li><code>FlushModeType.COMMIT</code>: 커밋 시에만 플러시</li>
</ul>
<p>플러시한다해서 영속성 컨텍스트에 보관된 엔티티를 지우는 것은 아니다.</p>
<hr>
<h2 id="36-준영속">3.6 준영속</h2>
<p><strong>준영속 상태</strong>: 영속성 컨텍스트가 관리하는 영속 상태의 엔티티가 <strong>영속성 컨텍스트에서 분리</strong>된 것을 말함. 준영속 상태에서는 <strong>영속성 컨텍스트가 제공하는 기능을 사용할 수 없음</strong>.</p>
<blockquote>
<p><strong>준영속 상태로 만드는 방법</strong></p>
</blockquote>
<p><strong>1-<code>detach()</code></strong>
메소드 호출 시 1차 캐시와 쓰기 지연 SQL 저장소에 있는 해당 엔티티 관리를 위한 모든 정보가 제거된다. 쓰기 지연 SQL 저장소의 INSET SQL조차 사라지기에 데이터베이스에 저장조차 되지 않을 수 있다. </p>
<p><strong>2-<code>clear()</code></strong>
<code>em.clear()</code> 메소드는 영속성 컨텍스트를 초기화하는 메소드로 해당 영속성 컨텍스트 내 <strong>모든 엔티티를 준영속 상태</strong>로 만듦. 모든 것이 초기화 되는 것이기에 영속성 컨텍스트를 제거하고 새로 만든 것과 같다.</p>
<p><strong>3-close()</strong>
영속성 컨텍스트 종료 시 해당 영속성 컨텍스트가 관리하던 영속성 상태의 엔티티가 <strong>모두 준영속 상태</strong>가 됨.</p>
<blockquote>
<p><strong>준영속 상태의 특징</strong></p>
</blockquote>
<ul>
<li>비영속 상태에 가까움: 1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩 포함 영속성 컨텍스트가 제공하는 어떤 기능도 동작하지 않음</li>
<li>식별자값을 가짐: 비영속과는 달리 준영속 상태는 한 번 영속 상태였으므로 <strong>반드시 식별자 값을 가지고 있음</strong></li>
<li>지연 로딩 불가: 실체 객체 대신 프록시 객체를 로딩해두고 해당 객체 실제 사용 시 영속성 컨텍스트를 통해 데이터를 불러오는 것이 불가능함.</li>
</ul>
<blockquote>
<p><strong>병합(merge)</strong></p>
</blockquote>
<p>준영속 상태의 엔티티를 <strong>다시 영속 상태</strong>로 변경하는 방법으로 준영속 상태의 엔티티를 받아 <strong>새로운 영속상태의 엔티티를 반환</strong>한다. 받은 준영속 엔티티를 영속 상태로 바꾸는 것이 아니라 새로 만들어 반환하는 식이다.</p>
<p><strong>과정</strong>
1: <code>merge()</code> 실행
2: 파라미터로 넘어온 <strong>준영속 엔티티</strong>의 식별자 값으로 1차 캐시에서 엔티티 조회 
    2-1: 1차캐시에 존재하지 않을 시 데이터베이스에서 조회한 후 1차 캐시에 저장
3: 조회한 영속 엔티티에 member 엔티티의 값을 채워넣음
4: 영속 엔티티를 반환</p>
<p><code>em.contains()</code>의 메소드는 영속성 컨텍스트가 파라미터로 넘어온 엔티티를 관리하고 있는지 확인할 수 있는 메소드이다.</p>
<p><strong>비영속 병합</strong>: 병합은 비영속 엔티티도 영속 상태로 만들 수 있는 메소드이다. 파라미터로 넘어온 엔티티의 식별자 값으로 영속성 컨텍스트와 데이터베이스를 조회하고 둘 모두에서 없으면 <strong>새로운 엔티티를 생성해 반환</strong>한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[인프런 데이터베이스] 2주차 스터디]]></title>
            <link>https://velog.io/@summeryoung_/%EC%9D%B8%ED%94%84%EB%9F%B0-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-2%EC%A3%BC%EC%B0%A8-%EC%8A%A4%ED%84%B0%EB%94%94</link>
            <guid>https://velog.io/@summeryoung_/%EC%9D%B8%ED%94%84%EB%9F%B0-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-2%EC%A3%BC%EC%B0%A8-%EC%8A%A4%ED%84%B0%EB%94%94</guid>
            <pubDate>Sun, 22 Mar 2026 02:27:41 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="인프런-데이터베이스-강의-섹션4-섹션5-기록">인프런 데이터베이스 강의 (섹션4-섹션5) 기록</h3>
</blockquote>
<h1 id="테이블의-생성">테이블의 생성</h1>
<p>표를 만드는 부분의 SQL을 직접 짜는 경우는 적다고한다. 데이터베이스의 장점은 컬럼에 데이터 타입을 강제(지정) 할 수 있음<br>
만드려는 표: <code>id</code>, <code>title</code>, <code>description</code>, <code>created</code>, <code>author</code>, <code>profile</code></p>
<pre><code class="language-sql">CREATE TABLE topic(
    -&gt;  id INT(11) NOT NULL AUTO_INCREMENT,
    -&gt;  title VARCHAR(100) NOT NULL,
    -&gt;  description TEXT NULL,
    -&gt;  created DATETIME NOT NULL,
    -&gt;  author VARCHAR(15) NULL,
    -&gt;  profile VARCHAR(200) NULL,
    -&gt;  PRIMARY KEY(id));</code></pre>
<ul>
<li><code>id INT(11)</code>: 뒤의 11의 의미는 11자리만 <strong>출력</strong>되게 하겠다는 의미(저장X). <code>INT</code>는 해당 열의 데이터 타입이 정수형이라는 뜻.</li>
<li><code>NOT NULL</code>: 해당 열의 정보가 널(NULL)이면 안된다는 의미. 해당 열에 값이 없으면 튜플을 추가하지 않고 거절함.</li>
<li><code>AUTO_INCREMENT</code>: 값이 자동으로 증가한다는 의미. id 값은 중복되면 안되기 때문에 자동으로 1씩 증가되도록 처리.</li>
<li><code>title VARCHAR(100)</code>: 100자리의 캐릭터까지만 저장한다는 의미. 그 뒤는 자른다.</li>
<li><code>description TEXT NULL</code>: 해당 값은 널이어도 되기에 <code>NULL</code> 지정.</li>
<li><code>DATETIME</code>: 연월일과 시간을 모두 기록하는 형식의 데이터 타입.</li>
<li><code>PRIMARY KEY</code>(기본키): 각 행을 구별할 수 있는 중요한 키로 중복을 방지함.</li>
</ul>
<img src="https://velog.velcdn.com/images/summeryoung_/post/eb532597-9c5a-46c8-a549-b1a39cc78ea3/image.png" width=80%>

<ul>
<li><code>SET PASSWORD</code>: 비밀번호 설정.</li>
</ul>
<hr>
<h1 id="sql의-crud">SQL의 CRUD</h1>
<p>CRUD는 <strong>C</strong>reate, <strong>R</strong>ead, <strong>U</strong>pdate, <strong>D</strong>elete의 약자.</p>
<h2 id="create">CREATE</h2>
<h3 id="insert">INSERT</h3>
<p>데이터를 추가하는 것을 create의 부분이라 할 수 있음.</p>
<blockquote>
<p><strong>INSERT INTO</strong> &lt;테이블명&gt; (속성1, 속성2, ...)
<strong>VALUES</strong> (값1, 값2...);</p>
</blockquote>
<p><code>SHOW DATABASE</code>: 데이터베이스들을 볼 수 있음
<code>SHOW TABLES</code>: 데이터베이스에 있는 테이블들을 볼 수 있음
<img src="https://velog.velcdn.com/images/summeryoung_/post/084a8c3e-77d8-4f0a-84ef-3c2b03a44d8d/image.png" width=70%></p>
<p><code>DESC &lt;테이블명&gt;</code>: 테이블의 속성과 정보를 살펴볼 수 있음.
<img src="https://velog.velcdn.com/images/summeryoung_/post/d8911a98-9e78-4fb2-8971-f7c890190ebe/image.png" width=70%></p>
<hr>
<pre><code class="language-sql">INSERT INTO topic (title, description, created, author, profile)
VALUES (&#39;MySQL&#39;, &#39;MySQL is...&#39;, NOW(), &#39;egoing&#39;, &#39;developer&#39;);</code></pre>
<img src="https://velog.velcdn.com/images/summeryoung_/post/f2d1c8a5-3e3d-4359-b2be-717d8a937d72/image.png">
- `id`: 값을 직접 입력해주지 않아도 자동으로 증가하기 때문에 생략.
- 속성의 순서와 값을 나열하는 순서가 동일해야함
- `NOW()`: 현재 시간을 자동으로 넣어줌.



<p><img src="https://velog.velcdn.com/images/summeryoung_/post/26fc54c4-3543-4b4d-a132-37b343da3232/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/3e4bbd7a-3e37-41ac-bc4c-526748c5846d/image.png" alt=""></p>
<ul>
<li><code>DROP &lt;테이블명&gt;</code>: 기존에 있던 테이블을 삭제할 수 있음.</li>
</ul>
<hr>
<h2 id="read">READ</h2>
<h3 id="select-문">SELECT 문</h3>
<p>SELECT 구문 뒤에 <strong>프로젝션</strong>, 즉 속성명을 적어주면 해당 속성(열)만 출력됨.</p>
<img src="https://velog.velcdn.com/images/summeryoung_/post/bb473d9e-bd2d-4af4-8201-af2d89604e7c/image.png" width=70%>

<h3 id="where-문">WHERE 문</h3>
<p>특정 값에 해당하는 행만 볼 때 사용하며 위치는 <strong>FROM</strong> 다음에 온다.
<img src="https://velog.velcdn.com/images/summeryoung_/post/e3312ecc-7340-45cf-8534-90277869ff6d/image.png" alt=""></p>
<h3 id="order-by-문">ORDER BY 문</h3>
<p>특정 값을 기준으로 정렬(오름차순/내림차순)을 할 때 사용. <code>DESC</code>를 사용하면 내림차순이고 <code>ASC</code> 혹은 생략하면 오름차순으로 정렬해준다.
<img src="https://velog.velcdn.com/images/summeryoung_/post/20218622-b738-46e2-a7d8-e17b96d248e7/image.png" alt=""></p>
<h3 id="limit">LIMIT</h3>
<p>방대한 데이터를 한 번에 다 가져와버리면 문제가 생길 수 있기에 가져와서 출력하는 데이터(행)의 양을 제한해준다.</p>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/f6f82a0b-c113-4911-9a85-ab3f11c25f8c/image.png" alt=""></p>
<hr>
<h2 id="update">UPDATE</h2>
<blockquote>
<p><strong>UPDATE</strong> &lt;테이블명&gt;
<strong>SET</strong> &lt;속성명&gt; = &lt;변경할값&gt;
<strong>WHERE</strong> &lt;수정할 행&gt;</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/dfc19a4a-9514-4088-ad54-2893ab7f1436/image.png" alt=""></p>
<ul>
<li>WHERE문을 빠뜨리면 해당 속성의 모든 값이 바뀌니 절대 주의.</li>
</ul>
<hr>
<h2 id="delete">DELETE</h2>
<blockquote>
<p><strong>DELETE</strong> 
<strong>FROM</strong> &lt;테이블명&gt;
<strong>WHERE</strong> &lt;조건&gt;</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/a2a6ff9f-c552-4d44-95dc-b914497e12b8/image.png" alt=""></p>
<ul>
<li>WHERE의 조건을 생략하면 테이블의 모든 행이 날아가버리니 주의.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터베이스 3주차 SQL 문제 풀이]]></title>
            <link>https://velog.io/@summeryoung_/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-3%EC%A3%BC%EC%B0%A8-SQL-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4</link>
            <guid>https://velog.io/@summeryoung_/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-3%EC%A3%BC%EC%B0%A8-SQL-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4</guid>
            <pubDate>Fri, 20 Mar 2026 11:55:00 GMT</pubDate>
            <description><![CDATA[<h2 id="sql-기본-문제-풀이">SQL 기본 문제 풀이</h2>
<blockquote>
<p><strong>Q1. 도서번호가 1인 도서의 이름을 검색하시오.</strong></p>
</blockquote>
<pre><code class="language-sql">SELECT bookname
FROM book
WHERE bookid = 1;</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/748457d8-44fb-4df4-be20-a82e67af81ee/image.png" alt=""></p>
<blockquote>
<p><strong>Q2. 가격이 20,000원 이상인 도서의 이름을 검색하시오.</strong></p>
</blockquote>
<pre><code class="language-sql">SELECT bookname
FROM book
WHERE price &gt;= 20000;</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/e27df6a4-937a-43c8-8c26-3d0a6b90d8a9/image.png" alt=""></p>
<blockquote>
<p>*<em>Q3. 고객 &#39;박지성&#39;의 총 구매액을 구하시오. *</em></p>
</blockquote>
<pre><code class="language-sql">SELECT SUM(saleprice)
FROM orders AS O
    JOIN customer AS C
    ON O.custid = C.custid
WHERE name = &#39;박지성&#39;;</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/53529379-4de3-4712-a8a2-327461d56e57/image.png" alt=""></p>
<span style="color:gray; font-size:80%">
  처음에 <code>SELECT SUM(price)</code>를 사용했는데 <code>price</code>라는 속성이 없다고 떠서 조인 사용하면`O.price`로 어떤 테이블의 속성인지 지정해줘야하나 싶어 수정.

<p>  <br> 그 뒤에 또 위에 적은대로 테이블명 적어서 수정했는데 여전히 <code>O.price</code>가 없다는 에러가 떠서 다시 생각해보니 속성명 자체가 price가 아니라 saleprice여서 수정 (<code>O.saleprice</code>로 안적고 속성명만 적어도 되지만 <code>O.saleprice</code>로 적어도 잘 동작한다.) 
</span></p>
<blockquote>
<p><strong>Q4. 고객 &#39;박지성&#39;이 구매한 도서의 수를 구하시오.</strong></p>
</blockquote>
<pre><code class="language-sql">SELECT COUNT(*)
FROM orders AS O
    JOIN customer AS C
    ON O.custid = C.custid
WHERE name = &#39;박지성&#39;;</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/2812cd4b-af87-4432-824a-dd8a1c627769/image.png" alt=""></p>
<blockquote>
<p>*<em>Q5. 5. 고객 &#39;박지성&#39;이 구매한 도서의 출판사 수를 구하시오. (서브쿼리를 활용할 것) *</em></p>
</blockquote>
<pre><code class="language-sql">SELECT COUNT(DISTINCT publisher) AS &#39;구매 도서 출판사 수&#39;
FROM orders AS O
    JOIN book AS B
    ON O.bookid = B.bookid
WHERE custid IN (
    SELECT custid
    FROM customer
    WHERE name = &#39;박지성&#39;
);</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/72826870-cd39-4204-bf42-a6bd0c95f6c5/image.png" alt=""></p>
<p>조인 안쓰고? 아래처럼도 풀 수 있을 것 같다.</p>
<pre><code class="language-sql">SELECT COUNT(DISTINCT publisher)
FROM book
WHERE bookid IN (
    SELECT bookid
    FROM orders
    WHERE custid IN (
        SELECT custid
        FROM customer
        WHERE name = &#39;박지성&#39;
));</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/6f9f039d-d5af-4854-abbc-6346a37022c6/image.png" alt=""></p>
<blockquote>
<p><strong>Q7. 고객 &#39;박지성&#39;이 구매하지 않은 도서의 이름을 검색하시오.</strong></p>
</blockquote>
<pre><code class="language-sql">SELECT *
FROM book
WHERE bookid NOT IN (
    SELECT bookid
    FROM customer AS C
        JOIN orders AS O
        ON C.custid = O.custid
    WHERE name=&#39;박지성&#39;
);</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/82546928-05cc-4c6d-ab9b-4e755bd3a5cb/image.png" alt=""></p>
<p>제대로 조회했는지 확인 위해서 일단 전체 조회를 위에처럼 해주고, SELECT에서 책 이름만 선택하도록 수정해 아래처럼 작성해주었다.</p>
<pre><code class="language-sql">SELECT bookname
FROM book
WHERE bookid NOT IN (
    SELECT bookid
    FROM customer AS C
        JOIN orders AS O
        ON C.custid = O.custid
    WHERE name=&#39;박지성&#39;
);</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/177f218a-fd97-4d1e-83ee-ca341b7caa79/image.png" alt=""></p>
<hr>
<h2 id="선택-프로그래머스-sql-문제">(선택) 프로그래머스 SQL 문제</h2>
<blockquote>
<p><strong>Q. 강원도에 위치한 생산공장 목록 출력하기</strong></p>
</blockquote>
<pre><code class="language-sql">-- 코드를 입력하세요
SELECT FACTORY_ID, FACTORY_NAME, ADDRESS
FROM FOOD_FACTORY
WHERE ADDRESS LIKE &#39;%강원도%&#39;
ORDER BY FACTORY_ID;</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/9fbda337-f7dd-40bc-904b-3c21abcf52df/image.png" alt=""></p>
<blockquote>
<p><strong>Q. 고양이와 개는 몇 마리 있을까?</strong></p>
</blockquote>
<pre><code class="language-sql">SELECT ANIMAL_TYPE, COUNT(DISTINCT ANIMAL_ID) AS count
FROM ANIMAL_INS
WHERE ANIMAL_TYPE = &#39;Cat&#39; OR ANIMAL_TYPE = &#39;Dog&#39;
GROUP BY ANIMAL_TYPE
ORDER BY ANIMAL_TYPE;</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/9c23b1a1-5b59-4f3a-9dc2-833058aa6a0f/image.png" alt=""></p>
<blockquote>
<p><strong>Q. 재구매가 일어난 상품과 회원리스트 구하기</strong></p>
</blockquote>
<pre><code class="language-sql">-- 코드를 입력하세요
SELECT USER_ID, PRODUCT_ID
FROM ONLINE_SALE
GROUP BY USER_ID, PRODUCT_ID
HAVING COUNT(*) &gt;= 2
ORDER BY USER_ID ASC, PRODUCT_ID DESC;</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/6daaf92a-3269-4789-923a-16c1c597476d/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터베이스 3주차 내용 정리]]></title>
            <link>https://velog.io/@summeryoung_/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-3%EC%A3%BC%EC%B0%A8-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@summeryoung_/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-3%EC%A3%BC%EC%B0%A8-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 19 Mar 2026 14:40:38 GMT</pubDate>
            <description><![CDATA[<h1 id="sql">SQL</h1>
<h2 id="개념-정리">개념 정리</h2>
<h3 id="sqlstructured-query-language">SQL(Structured Query Language)</h3>
<p>:관계 데이터베이스(RDBMS)를 위한 표준 질의어이자 <strong>비절차적 데이터 언어</strong>이다.</p>
<p><strong>비절차적인 언어</strong>: 데이터를 조회하거나 조작하는데 필요한 조건을 기술하긴 하지만, 어떻게 데이터를 찾고 처리하는지와 같은 <strong>실행 절차를 직접 명시하지 않음</strong></p>
<h3 id="분류">분류</h3>
<p><strong>데이터 정의어(DDL)</strong>: 테이블을 생성하고 변경/삭제하는 기능을 제공
    예) <code>CREATE</code>, <code>ALTER</code>, <code>DROP</code></p>
<p><strong>데이터 조작어(DML)</strong>: 테이블에 새 데이터를 <strong>삽입</strong>하거나 테이블에 저장된 데이터를 <strong>수정/삭제/검색</strong>하는 기능 제공
    예) <code>SELECT</code>, <code>INSERT</code>, <code>DELETE</code>
<strong>데이터 제어어(DCL)</strong>: 보안을 위해 데이터에 대한 <strong>접근 및 사용 권한</strong>을 사용자별로 부여하거나 취소하는 기능 제공.
    예) <code>GRANT</code>, <code>REVOKE</code></p>
<hr>
<h1 id="데이터-조작어-dml">데이터 조작어 (DML)</h1>
<h2 id="1-select-문">1. SELECT 문</h2>
<p>: 데이터를 검색하는 기본 문장으로, 결과를 테이블 형태로 출력함.</p>
<pre><code class="language-sql">SELECT [ALL | DISTINCT] &lt;속성이름(들)&gt;
FROM &lt;테이블이름(들&gt;

SELECT name, custid
FROM customer;</code></pre>
<ul>
<li><strong>ALL</strong>: 결과 테이블의 튜플 중복 허용 (기본)_</li>
<li><strong>DISTINCT</strong>: 결과 테이블 튜플의 중복 허용하지 않도록 지정</li>
</ul>
<pre><code class="language-sql">SELECT [ALL | DISTINCT] &lt;속성이름(들)&gt;
FROM &lt;테이블이름(들&gt;
WHERE &lt;검색조건(들): 단, 수식이나 함수 불가&gt;
GROUP BY &lt;속성 이름&gt;
HAVING &lt;검색조건(들): 수식이나 함수 포함된 것들
ORDER BY &lt;속성 이름&gt; [ASC | DESC]</code></pre>
<p>예) 모든 도서의 이름과 가격 검색하기
<img src="https://velog.velcdn.com/images/summeryoung_/post/eb62b641-494b-4e6c-bc6a-4845cc7f4c03/image.png" alt=""></p>
<h3 id="where-조건">WHERE 조건</h3>
<p>: 해당 <strong>조건에 부합하는 튜플만</strong> 출력 (수식이나 함수 작성 X)</p>
<br>
1. WHERE 절에 사용할 수 있는 술어: 

<ul>
<li>비교 기호(&gt;, &lt;, &gt;=, =)</li>
<li>범위: <code>BETWEEN</code></li>
<li>집합: <code>IN</code>, <code>NOT IN</code></li>
<li>패턴: <code>LIKE</code></li>
<li>이외: <code>NULL</code>, <code>AND</code>, <code>OR</code>, <code>NOT</code></li>
</ul>
<pre><code class="language-sql">SELECT *
FROM book

---BETWEEN 사용
WHERE price BETWEEN 10000 AND 20000;

---IN/ NOT IN 사용
WHERE publisher IN (&#39;대한미디어&#39;, &#39;굿스포츠&#39;);</code></pre>
<ol start="2">
<li>패턴/복합 조건 사용</li>
</ol>
<ul>
<li><p>LIKE와 와일드 문자를 사용해 문자열 검색 가능 </p>
</li>
<li><p>와일드 문자 사용 시, <code>=</code> 기호 보다 <code>LIKE</code> 사용.</p>
</li>
<li><p><code>AND</code> 또는 <code>OR</code> 사용 가능</p>
</li>
<li><p>와일드 문자: </p>
<ul>
<li><code>%</code>: 0개 이상의 문자열과 일치
  예) &#39;%축구%&#39;: 축구를 포함하는 문자열</li>
<li><code>_</code>: 특정 위치에 있는 1개의 문자와 일치
  예) &#39;_구%&#39;: 두 번째 위치에 &#39;구&#39;가 들어가는 문자열</li>
</ul>
</li>
</ul>
<pre><code class="language-sql">SELECT bookname
FROM book
WHERE bookname LIKE &#39;%축구%&#39;;

SELECT bookname
FROm book
WHERE bookname LIKE &#39;_구%&#39;;</code></pre>
<h3 id="order-by">ORDER BY</h3>
<p>정렬검색: SQL문 실행 결과를 특정 순서대로 정렬</p>
<p>기본은 오름차순(<strong>ASC</strong>)이며, 내림차순으로 정렬할 때는 <strong>DESC</strong> 사용. 널 값은 오름차순에서는 맨 마지막, 내림차순에서는 맨 먼저 출력됨.</p>
<pre><code class="language-sql">SELECT bookname
FROM book
ORDER BY bookname DESC;

---순서가 같은 결과가 있을 때, 두 번째 정렬기준으로 정렬하는 법
SELECT bookname
FROM book
ORDER BY bookname DESC, price ASC;</code></pre>
<hr>
<h2 id="2-집계함수와-group-by">2. 집계함수와 GROUP BY</h2>
<h3 id="집계함수">집계함수</h3>
<p>: 특정 <strong>속성</strong> 값을 통계적으로 계산하는 함수로 <strong>WHERE절에서는 사용불가하고 SELECT나 HAVING절에서만 사용 가능</strong></p>
<p>예) SUM, AVG, COUNT 같은 합계, 평균, 갯수</p>
<br>

<p>집계함수 종류</p>
<table>
<thead>
<tr>
<th align="center">집계함수</th>
<th align="center">문법</th>
<th align="center">예</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><code>SUM</code></td>
<td align="center"><code>SUM(&lt;속성이름&gt;)</code></td>
<td align="center"><code>SUM(price)</code></td>
</tr>
<tr>
<td align="center"><code>AVG</code></td>
<td align="center"><code>AVG([&lt;속성이름&gt;)</code></td>
<td align="center"><code>AVG(price)</code></td>
</tr>
<tr>
<td align="center"><code>COUNT</code></td>
<td align="center"><code>COUNT[&lt;속성이름/*&gt;]</code></td>
<td align="center">COUNT(*)</td>
</tr>
</tbody></table>
<p>외에도 <code>MAX</code>, <code>MIN</code> 등의 집계함수가 존재하고, 속성을 택할 때 ALL/DISTINCT 선택 가능.</p>
<p><code>COUNT</code>의 경우 책의 갯수나 튜플의 갯수를 세는데 사용 가능.</p>
<pre><code class="language-sql">SELECT SUM(saleprice) AS 총매출
FROM orders;</code></pre>
<p>위처럼 집계함수를 사용해서 나오는 결과 열(컬럼)의 이름을 <code>AS</code>를 통해 지정해줄 수 있음</p>
<h3 id="count">COUNT</h3>
<p>: 행의 개수를 세며 괄호 안에는 * 또는 특정 속성의 이름을 사용. </p>
<p>COUNT(*)는 널(NULL)까지 포함해 <strong>전체 튜플의 개수</strong>를 세고, COUNT(&lt;속성&gt;)의 경우 <strong>널(NULL)을 제외</strong>한다.</p>
<h3 id="group-by">GROUP BY</h3>
<p>: <strong>특정 속성의 값이 같은 튜플</strong>을 모아 그룹을 만든 후 검색</p>
<p>예) 고객별로 주문한 도서의 총수량과 총판매액 구하기</p>
<pre><code class="language-sql">SELECT COUNT(*) AS 도서수량,
       SUM(saleprice) AS 총액
FROM orders
GROUP BY custid
ORDER BY custid;</code></pre>
<p><span style="color:gray; font-size:80%">위의 sql문 같은 경우 순서는 위처럼 작성하지만, 실제로 실행되는 순서는 FROM -&gt; GROUP BY -&gt; SELECT -&gt; ORDER BY 순서대로 진행된다. SELECT가 나중에 실행되기 때문에 여기서 정하는 별칭 등은 아래에서 사용할 수 없다.</span></p>
<h3 id="having">HAVING</h3>
<p>: GROUP BY 절의 결과에 나타나는 그룹을 제한.</p>
<blockquote>
<p><strong>GROUP BY와 HAVING 사용 주의사항</strong>
<br><strong>GROUP BY &lt;속성&gt;</strong>: 튜플을 그룹으로 묶으면 SELECT 절에는 GROUP BY에서 사용한 속성과 집계함수만 나올 수 있음. <br>
<strong>HAVING &lt;검색조건&gt;</strong>: WHERE절과 HAVING절이 같이 포함된 SQL문은 검색조건이 모호해 질 수 있기에 HAVING절은 <br></p>
</blockquote>
<ol>
<li>반드시 GROUP BY 절과 함께 작성해야함</li>
<li>WHERE 절보다 뒤에 나와야함</li>
<li>검색조건에는 집계함수가 나와야함</li>
</ol>
<p>예)</p>
<pre><code class="language-sql">SELECT custid, COUNT(*) AS 도서수량
FROM orders
WHERE saleprice &gt;= 8000
GROUP BY custid
HAVING COUNT(*) &gt;= 2;
ORDER BY custid;</code></pre>
<p><span style="color:gray; font-size:80%">위 sql문의 실행순서는 FROM -&gt; WHERE -&gt; GROUP BY -&gt; HAVING -&gt; SELECT -&gt; ORDER BY 순이다.</span></p>
<hr>
<h2 id="3-조인join">3. 조인(JOIN)</h2>
<p>조인검색: 여러 테이블을 <strong>연결</strong>해 데이터를 검색하는 것
<span style="color:gray; font-size:80%">카티션 프로덕트를 사용해도 연결할 수 있지만, 데이터가 많아질수록 방대한? 연산?이 필요해지는 식이므로 조인을 사용하는 것이 좋다..!</span></p>
<blockquote>
<p><strong>❗ 주의점</strong>
연결하려는 테이블 간에 <strong>조인 속성의 이름은 달라도 되지만 도메인은 같아야함</strong>
일반적으로 <strong>외래키</strong>를 조인속성으로 사용함</p>
</blockquote>
<pre><code class="language-sql">SELECT *
FROM customer AS C
    JOIN orders AS O 
    ON C.custid = O.custid
WHERE custid BETWEEN 1 AND 4;</code></pre>
<p>위 예시처럼 테이블에 <strong>AS</strong>를 사용해 별칭을 지정해 줄 수 있다.
<span style="color:gray; font-size:80%">FROM절에서 지정한 별칭은 SELECT에서 사용해도 되지만, SELECT에서 지정한 별칭을 아래에서 사용하는것은 불가. 별칭인 C, O를 <strong>튜플 변수</strong>라고도 한다.</span></p>
<h3 id="외부조인">외부조인</h3>
<p>: 조인 조건을 만족하지 않는 튜플에 대해서도 검색을 수행하는 조인.</p>
<p>완전/왼쪽/오른쪽이 존재한다. 각각 전부에서 만족하지 않는 튜플에 대해서도 가져올지, 왼쪽에서만 또는 오른쪽에서만 가져올지를 결정한다.</p>
<pre><code class="language-sql">---제대로 안돌아가는 코드
SELECT C.name, C.saleprice
FROM customer AS C
    LEFT OUTER JOIN orders AS O
    ON C.custid = O.custid
WHERE custid = 1;</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/3c941eb0-8369-48cd-bc85-aac2a8ece729/image.png" alt=""></p>
<p><span style="color:gray; font-size:80%">조인하고 나서 WHERE에서 속성 선택할 때 테이블명.속성 이렇게 지정해줘야할 것 같다. 공통 속성이면 둘 다 존재하니까. 그리고 SELECT할 때도 조인 후에도 잘 선택해줘야할 것 같다. (처음에 C.saleprice해서 오류)</span></p>
<p>예) 고객과 고객의 주문에 관한 데이터를 고객별로 정렬해 나타내기
<img src="https://velog.velcdn.com/images/summeryoung_/post/1a1b43e0-a6d8-4837-81cc-b2324d60f68c/image.png" alt=""></p>
<hr>
<h2 id="sql-클린-코드">SQL 클린 코드</h2>
<h3 id="코드-스타일">코드 스타일</h3>
<ul>
<li>키워드 대문자: <code>SELECT</code></li>
<li>식별자 소문자: <code>orders</code>, <code>customer_id</code></li>
<li>별칭: 짧고 의미있는 이름</li>
<li>들여쓰기와 줄바꿈: 절마다 줄바꿈, 조건은 들여쓰기</li>
<li>주석: <code>-</code> 또는 <code>/**/</code>를 통해 설명 추가</li>
</ul>
<h3 id="네이밍-규칙">네이밍 규칙</h3>
<ul>
<li>일관성 유지: 같은 개념은 동일한 이름 사용
  예) <code>customer_id</code>와 <code>cust_id</code> 혼용X</li>
<li>snake_case 권장</li>
<li>명확한 의미</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Altu-Bitu/알고리즘] 1주차 정렬, 맵, 셋 문제풀이]]></title>
            <link>https://velog.io/@summeryoung_/Altu-Bitu%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-1%EC%A3%BC%EC%B0%A8-%EC%A0%95%EB%A0%AC-%EB%A7%B5-%EC%85%8B-%EB%AC%B8%EC%A0%9C%ED%92%80%EC%9D%B4</link>
            <guid>https://velog.io/@summeryoung_/Altu-Bitu%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-1%EC%A3%BC%EC%B0%A8-%EC%A0%95%EB%A0%AC-%EB%A7%B5-%EC%85%8B-%EB%AC%B8%EC%A0%9C%ED%92%80%EC%9D%B4</guid>
            <pubDate>Sun, 15 Mar 2026 15:28:23 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>03.13(금)-03.15(일) 기록</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/47626898-30e9-4a67-927a-136f8286a30f/image.png" alt=""></p>
<h3 id="boj-19636번-요요-시뮬레이션">BOJ 19636번: 요요 시뮬레이션</h3>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;cmath&gt;
#include &lt;string&gt;
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    long long w0, I0, T;
    cin &gt;&gt; w0 &gt;&gt; I0 &gt;&gt; T;

    long long days, I, A;
    cin &gt;&gt; days &gt;&gt; I &gt;&gt; A;

    long long D=I0;
    long long weight = w0;
    string result=&quot; &quot;;
    bool danger = false;


    for (int i=0; i&lt;days; i++) {
        weight += I-(D + A);
        if (weight &lt;=0 ) {
            cout &lt;&lt; &quot;Danger Diet\n&quot;;
            danger = true;
            break;
        }
    }
    if (!danger) cout &lt;&lt; weight &lt;&lt; &quot; &quot; &lt;&lt; D &lt;&lt; &quot;\n&quot;;

    weight = w0;
    D = I0;
    for (int i=0; i&lt;days; i++) {
        weight += I-(D+A);
        if (weight &lt;=0 || D &lt;=0) {
            cout &lt;&lt; &quot;Danger Diet\n&quot;;
            return 0;
        }

        if (abs(I-(D+A)) &gt; T) {
            D += (long long)floor((I-(D+A))/2.0);
        }

        if (D&lt;=0) {
            cout &lt;&lt; &quot;Danger Diet&quot;;
            return 0;
        }
    }

   if (I0-D &gt; 0) result = &quot;YOYO&quot;;
   else result = &quot;NO&quot;;
    cout &lt;&lt; weight &lt;&lt; &quot; &quot; &lt;&lt; D &lt;&lt; &quot; &quot; &lt;&lt; result;
}</code></pre>
<p>진짜 처음에 엄청 헤맸다. 근데 사실 진짜 쓸데없이 헤맸다. 이번주가 정렬, 맵, 셋이라 무조건 이 셋 중 하나의 개념을 사용해야한다고 생각했는데...?</p>
<p>도대체 왜? ㅎㅎ 그냥 주어진 조건, 수학적 식 잘 사용해서 구현하는 문제였다.</p>
<p>주의할 부분은 <code>floor()</code> 사용해주는 부분. 내림 처리하는 것인 것같고... 어디서 <code>D</code>, 즉 일일 활동 대사량(?) 단어가 정확히 기억이 안나지만, 그걸 매일 업데이트하는 시점을 주의해서 해주어야한다는 부분이라고 생각한다.</p>
<p>시간 제한이 막 빡세지는 않아서 그냥 반복문 같은 걸 두 번 돌려서 기초 대사량 변화 있을 때, 없을 때의 결과를 출력했고.</p>
<p>하다보니 많이 틀리고 오류냈던 부분은 <code>Danger Diet</code>를 두 케이스에서 어떤 시점에 어떻게 판별할지 부분이었던 것 같다.</p>
<p>=&gt; 처음에 잘못시작해서 아예 지우고 다시 시작하는게 더 빨랐다.</p>
<hr>
<h3 id="boj-11478번-서로-다른-부분-문자열의-개수">BOJ 11478번: 서로 다른 부분 문자열의 개수</h3>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;set&gt;
using namespace std;

int main() {
    string s;
    cin &gt;&gt; s;

    set&lt;string&gt; arr;
    for (int i=1; i&lt;=s.length(); i++) {
        for (int j=0; j+i&lt;=s.length(); j++) {
            string tmp = s.substr(j,i);
            arr.insert(tmp);
        }
    }

    cout &lt;&lt; arr.size();
}</code></pre>
<p>이전에 봤었는데 못 풀었던 문제라 이번에 다시 봤을 때 풀 수 있을까? 싶긴했다. 일단 문자열 사용하는 문제였고, 문자열 조작하는건 여전히 까다로운 것 같다.</p>
<p>부분 문자열을 만들고, 그 중 중복되지 않는 부분 문자열의 갯수가 몇 개인지 세는 문제였다. 보자마자 생각한 것은 <code>substr()</code> 써서 부분 문자열 만드는 것과, 그러려면 반복문 써야하는데 시간 제한 확인해봐야겠다? 정도였다.</p>
<p>일단은 <code>substr()</code> 써서 부분 문자열 만들어서 <code>set</code>에 저장해버리면 알아서 중복된 문자열들은 새로 저장되지 않을테니 후에 set의 크기만 출력해주면 될거라고 생각했다.</p>
<p>그러면 1차로 멈췄던것은 시간제한이 1초였는데 <code>substr()</code> 만들 때 이중 반복문을 써야해서 였다. 겉에서는 <strong>문자 길이</strong> 반복 돌리고, 그 안에서 <strong>문자열 처음~마지막 인덱스까지</strong> 돌면서 부분 문자열 만드는 방식을 생각해서 반복문을 무조건 2번 사용해야했다. 다행인건 입력제한인 문자열의 길이가 1000이라 n=1000인걸 고려하면 O(n^2)이어도 시간제한 1초는 넘기지 않을 거라 그냥 사용했다.</p>
<p>이후는 그래도 나름 쉽게쉽게 풀어나갔던 것 같다. <code>substr()</code> 사용법 까먹어서 찾아서 다시 정리했다.</p>
<blockquote>
<p><strong><code>substr(pos, count)</code></strong>
<br><code>문자열.substr()</code>으로 사용하게 되며 <strong><code>pos</code>번째 문자부터 <code>count</code> 길이만큼의 문자열을 리턴</strong>해준다.
<br> 만약, 인자로 전달된 부분 문자열의 길이가 문자열보다 길면, 그 이상을 반환하지는 않고 문자열의 끝까지만 리턴해준다.
<br> 만약 count로 <code>npos</code>를 전달한다면, 자동으로 <code>pos</code>부터 원래 문자열의 끝까지 리턴.</p>
</blockquote>
<p>막상 IDE에서 돌렸을 때 생각처럼 결과가 안나와서 어떻게 할까하다가 만든 <code>substr()</code>을 바로 셋에 저장하지 않고 <code>tmp</code>라는 변수에 저장하도록해서 디버깅을 해봤다. CLion IDE에서는 따로 조작하지 않아도 변수에 어떤 값 들어가는지는 잘 보여서 이렇게 하면 확인이 쉬웠다.</p>
<p>처음에 잘못했던 부분은 <code>pos</code>는 굳이 설정+업데이트 하지 않고 안쪽 반복문인 <code>j</code>를 사용해야한다는점. 이게 부분 문자열로 사용됐다고 그걸 넘어가는게 아니라 그냥 <code>j</code>의 업데이트를 따라가면 됐었다.</p>
<p>그리고 마지막에 틀린건, 이중 반복문 둘 다 조건식에서 <code>s.length()</code> 미만이 아니라 <strong>이하</strong>로 설정해줘야하는 부분이었다. 겉의 반복문인 <code>i</code>가 1에서부터 돌기 때문에 그것도 고려해줘야했다.</p>
<hr>
<h3 id="boj-1431번-시리얼-번호">BOJ 1431번: 시리얼 번호</h3>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;algorithm&gt;
using namespace std;

int addNum(string s) {
    int sum = 0;

    for (char i : s) {
        if (isdigit(i)) sum+= i-&#39;0&#39;;
    }

    return sum;
}
bool comp (string s1, string s2) {

    if (s1.length() == s2.length()) {
        if (addNum(s1) == addNum(s2)) {
            return s1 &lt; s2;
        }
        return addNum(s1) &lt; addNum(s2);
    }

    return s1.length() &lt; s2.length();
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int n;
    cin &gt;&gt; n;

    vector&lt;string&gt; serial(n);
    for (int i=0; i&lt;n; i++) {
        string s;
        cin &gt;&gt; s;

        serial[i] = s;
    }

    sort(serial.begin(), serial.end(),comp);

    for (string s : serial) cout &lt;&lt; s &lt;&lt; &quot;\n&quot;;
}</code></pre>
<p>이거는 그래도 문제 보자마자 <code>sort()</code>에서 <code>comp()</code> 잘 정의해서 정렬해주면 되는거라고 생각해서 풀 수 있었다. 앞에서 한 번 써본거라 사용했는데 차이는 문자열에 들어있는 숫자의 경우 합을 비교해야하는거라 그 합을 리턴해주는 함수를 하나 추가해서 사용했다는 것 정도. <code>comp()</code> 정의하는 것을 배우고 다시 사용하면서 익힐 수 있는 문제여서 좋았다. <code>false</code> 값을 받을 때 스왑하게 되는 것을 주의.</p>
<hr>
<h3 id="boj-9375번-패션왕-신해빈">BOJ 9375번: 패션왕 신해빈</h3>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;map&gt;
#include &lt;set&gt;
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int t;
    cin &gt;&gt; t;

    while (t--) {
        int n;
        cin &gt;&gt; n;

        map&lt;string,int&gt; clothes;
        set&lt;string&gt; types;
        for (int i=0; i&lt;n; i++) {
            string s, type;
            cin &gt;&gt; s &gt;&gt; type;

            types.insert(type);
            auto it = clothes.find(type);
            if (it != clothes.end()) {
                clothes[type] = clothes.find(type)-&gt;second +1;
            }
            else clothes[type] = 1;

        }
        int sum = 1;
        map&lt;string, int&gt;::iterator iter;
        for (iter=clothes.begin(); iter !=clothes.end(); ++iter) {
            sum *= iter-&gt;second +1;
        }

        cout &lt;&lt; sum-1 &lt;&lt; &quot;\n&quot;;
    }
}</code></pre>
<p>의외로 수학적인 능력? 확통 생각을 많이 했던 문제. 아니 애초에 그냥 확통 생각하고 풀면 더 쉬웠을텐데 처음에 바보같은 짓을 했다.</p>
<p>근데 제대로 푼 것 같은데 계속 25%에서 틀렸다고 뜨길래 왠지 한참 봤는데... 출력할 때 <code>\n</code> 엔터를 빼먹었다. 왜 이 생각을 못했을까.</p>
<p>풀이는 그냥... 옷 가짓수 다 곱하는 확통 문제인데, 이제 알몸이면 안되니까 각 옷 종류 곱한 것에서 하나를 빼는 그런 식이다.</p>
<p>이전에 맵 배웠으니까, 옷 종류(<code>headgear</code>)를 기준으로 해당 옷 종류에 가짓수가 몇 개 있는지를 맵으로 저장해두면 되는 식이었다.</p>
<p>반복자를 이번에 또 다시 사용해봤는데, 이게 처음에 낯설어서 어렵지 몇 번 써보면 새삼 편한 것 같다.</p>
<hr>
<h3 id="boj-1946번-신입사원">BOJ 1946번: 신입사원</h3>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;vector&gt;
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int t;
    cin &gt;&gt; t;

    while (t--) {
        int n;
        cin &gt;&gt; n;

        vector&lt;int&gt; score(n+1);

        for (int i=0; i&lt;n; i++) {
            int s1, s2;
            cin &gt;&gt; s1 &gt;&gt; s2;

            score[s1] = s2;
        }

        int cnt = n;
        for (int i=n; i&gt;1; i--) {
            for (int j=i-1; j&gt;=1; j--) {
                if (score[i] &gt; score[j]) {
                    cnt--;
                    break;
                }
            }
        }

        cout &lt;&lt; cnt &lt;&lt; &quot;\n&quot;;
    }
}</code></pre>
<p>전에 풀었던 추월 문제를 생각하면서 짜서 의외로 간단하게 풀었다고 생각했는데 시간 초과가 났다... 일단 점수 2개 중에 하나는 정렬할 필요없이 그냥 해당 인덱스에다가 넣어서 처리하고, 이제 뒤에서부터 비교해서 만약 자신보다 작은 인덱스의 값이 자신의 값보다 작으면 둘 다 점수가 떨어지는 지원자니까 <code>cnt</code>에서 하나씩 빼는 식으로 했는데....? (처음에는 문제 잘못 이해해서 <code>cnt</code> 하나씩 더하는 식으로 했는데 이러면 한 명이라도 자기보다 무엇하나 못 본 사람이 있으면 뽑히는 식이라 충족되지 않는 거였다.)</p>
<p>근데 이렇게 풀었다해도 일단 시간초과났다. 아마 <code>for</code>문 돌려서 비교하는 쪽?의 문제 같긴한데ㅔ....</p>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;vector&gt;
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int t;
    cin &gt;&gt; t;

    while (t--) {
        int n;
        cin &gt;&gt; n;

        vector&lt;int&gt; score(n+1);

        for (int i=0; i&lt;n; i++) {
            int s1, s2;
            cin &gt;&gt; s1 &gt;&gt; s2;

            score[s1] = s2;
        }

        int cnt = 1;
        int high = score[1];

        for (int i=2; i&lt;=n; i++) {
            if (high &gt; score[i]) {
                cnt++;
                high = score[i];
            }
        }

        cout &lt;&lt; cnt &lt;&lt; &quot;\n&quot;;
    }
}</code></pre>
<p>입력 값이 10^5인데 반복문을 두 번 돌리면 O(n^2)이니까 1초에 10^8번 연산을 하기에 일단 시간초과가 나는건 당연했다.</p>
<p>for문 2번 돌리는 것을 어떻게 한 번으로 줄여야할 지 사실 몰라서 예시코드 살짝 보고 아이디어를 얻었다. 일단 하나의 순서를 고정하고 다른 쪽으로 판별하는건 맞았는데...</p>
<p>어차피 한 점수가 1등인 사람은 뽑히는것이 확정이니 그 사람의 두 번째 등수를 <strong><code>highest_rank</code>에 저장하고 밑의 사람들의 두 번째 종목 등수와 비교</strong>한다. </p>
<p>쉽게 생각하면 첫 번째 종목의 등수가 앞에 있는 사람들의 두 번째 종목 등수 중 <strong>가장 높은 것을 변수 하나에 기록</strong>해두고 그것과 n번째 사람의 두 번째 종목 등수를 비교해 n번째 사람의 등수가 더 낮으면 첫 번째 종목 등수가 앞인 모든 사람들보다 이 사람의 두 번째 종목 등수가 낮은 것이니 뽑히게 되는 것. 그리고 이 사람의 등수가 더 낮으니 <strong><code>highest_rank</code>를 업데이트</strong>해주는 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[밑시딥1] 챕터2 내용 정리]]></title>
            <link>https://velog.io/@summeryoung_/%EB%B0%91%EC%8B%9C%EB%94%A51-%EC%B1%95%ED%84%B02-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@summeryoung_/%EB%B0%91%EC%8B%9C%EB%94%A51-%EC%B1%95%ED%84%B02-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sun, 15 Mar 2026 12:59:42 GMT</pubDate>
            <description><![CDATA[<h1 id="챕터2-퍼셉트론">챕터2. 퍼셉트론</h1>
<p><strong>퍼셉트론</strong>은 현재 신경망(딥러닝)의 기원이 되는 알고리즘.</p>
<h2 id="21-퍼셉트론이란">2.1 퍼셉트론이란?</h2>
<p><strong>퍼셉트론</strong>은 다수의 신호를 받아 하나의 신호를 출력하며, 0 또는 1, 두 가지 값만을 가질 수 있다.</p>
<img src="https://velog.velcdn.com/images/summeryoung_/post/d97a90d1-0e9b-4de8-8a96-60c7c9e3b523/image.png" width=40%>

<p>입력으로 2개의 신호를 받는 퍼셉트론은 위와 같다. </p>
<p>입력신호: $x_{1}$, $x_{2}$ 
출력신호: $y$
<strong>가중치</strong>: $w_{1}$, $w_{2}$</p>
<p>입력 신호가 뉴런에 보내질 때에는 <strong>각각 고유한 가중치가 곱해진다</strong>. 위처럼 입력과 가중치를 곱해 뉴런에서 보내온 <strong>신호의 총합이 정해진 한계가 넘어설 때만 1을 출력</strong>한다. 이 한계를 임곗값이라 하며, $\theta$ 세타 기호로 표기한다.</p>
<p>$\begin{cases} 0 (w_1x_1 + w_2x_2 \le \theta) \ \ 1  (w_1x_1 + w_2x_2 &gt; \theta)\end{cases}$</p>
<p>퍼셉트론은 복수의 입력 신호 각각에 <strong>고유한 가중치</strong>를 부여함. 가중치는 <strong>각 신호가 결과에 주는 영향력을 조절하는 요소</strong>로 작용한다.</p>
<h2 id="22-논리-회로">2.2 논리 회로</h2>
<h3 id="and-게이트">AND 게이트</h3>
<p>AND 게이트의 입력은 둘이고 출력이 하나. 진리표를 적어보면 아래와 같다.</p>
<table>
<thead>
<tr>
<th align="center">$x_1$</th>
<th align="center">$x_2$</th>
</tr>
</thead>
<tbody><tr>
<td align="center">0</td>
<td align="center">0</td>
</tr>
<tr>
<td align="center">1</td>
<td align="center">0</td>
</tr>
<tr>
<td align="center">0</td>
<td align="center">1</td>
</tr>
<tr>
<td align="center">1</td>
<td align="center">1</td>
</tr>
</tbody></table>
<p>위의 AND 게이트 역시 <strong>가중치</strong>와 <strong>임곗값</strong>을 조절하여 퍼셉트론으로 표현할 수 있으며, 이를 만족시키는 매개변수 조합은 무한히 많다. 예를 들면 (0.5, 0.5, 0.7)이 있다.</p>
<h3 id="nand-게이트와-or-게이트">NAND 게이트와 OR 게이트</h3>
<p>NAND 게이트는 Not AND를 의미하며 AND 게이트의 가중치와 임곗값을 반전하면 NAND 게이트를 구현할 수 있다. (-0.5, -0.5, -0.7)가 예이고, 진리표는 아래와 같다.</p>
<table>
<thead>
<tr>
<th align="center">$x_1$</th>
<th align="center">$x_2$</th>
</tr>
</thead>
<tbody><tr>
<td align="center">0</td>
<td align="center">0</td>
</tr>
<tr>
<td align="center">1</td>
<td align="center">0</td>
</tr>
<tr>
<td align="center">0</td>
<td align="center">1</td>
</tr>
<tr>
<td align="center">1</td>
<td align="center">1</td>
</tr>
</tbody></table>
<p>OR 게이트의 진리표는 아래와 같은데, 생각해봤을 때 (0.6, 0.6, 0.5)로 가중치와 임곗값을 조정하면? 아마 퍼셉트론으로 구현할 수 있을 것 같았다. <br><span style="color:gray; font-size:80%">(진리표 자체는 디지털논리설계에서 다뤘던 내용이라 익숙한데, 퍼셉트론으로 이런 게이트를 구현하는 거 자체가 새로워서, 후에 OR 게이트 가중치와 임곗값 설정한 것은 코드로 짜보고 확인할 예정).</span></p>
<table>
<thead>
<tr>
<th align="center">$x_1$</th>
<th align="center">$x_2$</th>
</tr>
</thead>
<tbody><tr>
<td align="center">0</td>
<td align="center">0</td>
</tr>
<tr>
<td align="center">1</td>
<td align="center">0</td>
</tr>
<tr>
<td align="center">0</td>
<td align="center">1</td>
</tr>
<tr>
<td align="center">1</td>
<td align="center">1</td>
</tr>
</tbody></table>
<blockquote>
<p>이때 퍼셉트론의 매개변수 값을 정하는 것은 컴퓨터가 아니라 <strong>인간</strong>이다. 인간이 직접 진리표라는 학습 데이터를 보면서 매개변수의 값을 생각하게된다. 머신러닝 문제는 이 <strong>매개변수의 값을 정하는 작업을 컴퓨터가 자동</strong>으로 하도록하고, 사람은 퍼셉트론의 구조(모델)를 고민하고 컴퓨터에 학습할 데이터를 주는 역할이다.</p>
</blockquote>
<p>퍼셉트론의 구조는 모두 동일해도 AND, NAND, OR 게이트를 모두 구현할 수 있고, 다른 것은 오직 <strong>매개변수 (가중치와 임곗값)</strong>이다. </p>
<h2 id="23-퍼셉트론-구현하기">2.3 퍼셉트론 구현하기</h2>
<p>해당 장에서는 논리회로를 파이썬을 통해서 구현해보는 작업을 한다.</p>
<pre><code class="language-python">def AND(x1, x2):
  w1, w2, theta = 0.5, 0.5, 0.7
  tmp = x1*w1+x2*w2

  if tmp &lt;= theta:
    return 0 #임곗값 이하면 0 (비활성화)
  elif tmp &gt; theta:
    return 1</code></pre>
<p>입력값을 매개변수로 받아 AND라는 함수를 실행시키는 식이다. 가중치와 임곗값은 변수에 미리 값을 저장하고, <code>tmp</code>라는 변수가 뉴런에서 전달하는 입력값*가중치의 총합을 담고, 해당 값과 임곗값을 비교해 0/1의 값을 전달하도록 하는 구조... 결과는 아래처럼 나온다.  </p>
<p>매개변수를 (0.1, 0.1, 0.1)로 바꾸어도 결과는 잘 나온다.
<img src="https://velog.velcdn.com/images/summeryoung_/post/ac233614-05ff-4432-8625-394b18753669/image.png" alt=""></p>
<h3 id="편향bias-도입">편향(bias) 도입</h3>
<p>앞에서 계속 임곗값으로 $\theta$로 사용했는데 해당 값을 $-b$로 치환해 사용한다는 내용이었다. 앞에서 사용했던 식도 아래처럼 변하게된다. 큰 차이는 없고, 우변에서 좌변으로 넘기는 것 정도이다.</p>
<p>다만 여기서의 $b$ 를 <strong>편향(bias)</strong>라고 부른다. </p>
<p>임곗값을 넘지 못하는지의 여부를 기준으로 0/1을 출력했다면, 이제는 <strong>신호와 가중치를 곱합 값의 총합과 편향을 합해 해당 값이 0을 넘으면 1을, 그렇지 못하면 0을 출력하는 방식</strong>이다.
<br>
$\begin{cases} 0 (b+ w_1x_1 + w_2x_2 \le 0) \ \ 1  (b+w_1x_1 + w_2x_2 &gt; 0)\end{cases}$
<br></p>
<p>넘파이 배열끼리의 곱셈은 두 배열의 원소 수가 같다면 <strong>각 원소끼리 곱하기에</strong> 이를 사용해 아래처럼 코드를 짜는 듯 하다.</p>
<p><code>np.sum()</code>을 사용하면 입력한 배열에 담긴 모든 원소의 총합을 계산한다. 이를 통해 뉴런에서 전달하는 입력*가중치의 총합을 계산하고 마지막에 편향을 더해준다.</p>
<pre><code class="language-python">import numpy as np

x = np.array([0,1]) #입력
w = np.array([0.5, 0.5]) #가중치
b = -0.7

np.sum(x*w)+b</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/7a5110c2-84a5-413f-b24a-753978da5396/image.png" alt=""></p>
<h3 id="가중치와-편향-구현">가중치와 편향 구현</h3>
<pre><code class="language-python">#가중치와 편향 구현한 AND 게이트
import numpy as np

def AND(x1,x2):
  x = np.array([x1,x2])
  w = np.array([0.5, 0.5])
  b = -0.7

  tmp = np.sum(x*w)+b
  if tmp &lt;= 0:
    return 0
  elif tmp &gt; 0:
    return 1

print(AND(0,0))
print(AND(1,0))
print(AND(0,1))
print(AND(1,1))</code></pre>
<img src="https://velog.velcdn.com/images/summeryoung_/post/8c957d3b-8bd7-4057-8f0b-a8dea253b531/image.png" width=70%>
위에서 반영한 편향이나 numpy를 사용한 배열의 곱과 총합 계산 메소드의 사용이 적용되어있다. **편향과 가중치의 기능이 다르다**. 

<p>가중치는 <strong>입력 신호가 결과에 주는 영향력(중요도)</strong>를 조절하는 매개변수라면, 
편향은 <strong>뉴런이 얼마나 쉽게 활성화(=결과를 1로 출력) 하느냐를 조정</strong>하는 매개변수이다.</p>
<p>NAND와 OR 게이트 역시 동일한 방식으로 작성해주었다. (아래는 OR 게이트만...)
<img src="https://velog.velcdn.com/images/summeryoung_/post/7c312b9c-017b-430e-99aa-9b154d05da9b/image.png" alt=""></p>
<h2 id="24-퍼셉트론의-한계">2.4 퍼셉트론의 한계</h2>
<p>게이트 중 <strong>XOR 게이트</strong>는 다만 하나의 퍼셉트론을 통해 구현할 수 없다.</p>
<table>
<thead>
<tr>
<th align="center">$x_1$</th>
<th align="center">$x_2$</th>
</tr>
</thead>
<tbody><tr>
<td align="center">0</td>
<td align="center">0</td>
</tr>
<tr>
<td align="center">1</td>
<td align="center">0</td>
</tr>
<tr>
<td align="center">0</td>
<td align="center">1</td>
</tr>
<tr>
<td align="center">1</td>
<td align="center">1</td>
</tr>
</tbody></table>
<p>지금까지 다룬 퍼셉트론은 모두 <strong>직선으로 나뉘는 두 영역을 만든</strong>다는 특징이 있다. 즉, 직선으로 영역을 나누어 한쪽 영역은 1, 다른 쪽은 0을 출력하도록 하는 의미다.</p>
<img src="https://velog.velcdn.com/images/summeryoung_/post/ac109d34-7f7b-4a2f-a662-b88ea502d67e/image.png" width=70%>

<p>위의 그림을 보면 파란점이 1, 빨간점이 0을 출력해야한다는 의미일 때, OR 게이트인 경우 선을 기준으로 0을 출력하는 영역, 1을 출력하는 영역으로 구분할 수 있다. </p>
<p>하지만, XOR 게이트의 경우 두 점을 직선을 경계로 나눌 수 없다. 즉, 직선 하나로 저 두 영역을 분리하는 것은 불가능하다.</p>
<h3 id="선형과-비선형">선형과 비선형</h3>
<p>위에서 <strong>직선</strong>으로 XOR의 영역을 표현할 수 없다고 하였다. 하지만 <strong>퍼셉트론은 직선 하나로 나눈 영역만 표현할 수 있다</strong>는 한계가 존재한다. 즉, 곡선은 표현이 불가능하다.</p>
<p>이런 곡선의 영역을 <strong>비선형</strong>, 직선의 영역을 <strong>선형</strong>이라한다.</p>
<h3 id="다층-퍼셉트론">다층 퍼셉트론</h3>
<p>하나의 퍼셉트론으로는 XOR 게이트를 나타낼 수 없다. 그래서 나온 것은 여러 퍼셉트론의 층을 쌓은 <strong>다층 퍼셉트론</strong>이다.</p>
<p>우선 XOR 게이트는 기존 게이트들을 조합해서 만들 수 있다.책에서는 NAND, AND, OR을 모두 하나씩 사용해 만들었고, 코드는 아래와 같다.
<img src="https://velog.velcdn.com/images/summeryoung_/post/04990d7d-cd1b-4f93-b70d-5b8a757527da/image.png" alt=""></p>
<pre><code class="language-python">def XOR (x1, x2):
  s1 = NAND(x1,x2)
  s2 = OR(x1,x2)
  tmp = AND(s1,s2)

  if tmp &lt;= 0:
    return 0
  elif tmp &gt; 0:
    return 1
</code></pre>
<br>

<p>XOR 게이트의 퍼셉트론을 살펴보면 아래와 같이 <strong>다층 구조</strong>이다.
앞에 나온 AND, OR가 단층 퍼셉트론인 것과 달리, XOR은 2층 퍼셉트론이다.</p>
<blockquote>
<p>퍼셉트론은 모두 3개의 층으로 구성되어있지만, <strong>가중치를 갖는 층은 실질적으로 2개라 2층 퍼셉트론</strong>이라고 한다고 한다.</p>
</blockquote>
<img src="https://velog.velcdn.com/images/summeryoung_/post/84be6ebc-fd5f-4067-b527-bd62ece71fea/image.png" width=60%>


<p>즉, XOR 게이트처럼 <strong>단층 퍼셉트론으로 표현하지 못하는 것들을 층을 늘려 구현</strong>할 수 있다. 퍼셉트론은 층을 더 깊이 쌓아 더 다양한 대상을 표현할 수 있다는 장점이 있다.</p>
<hr>
<blockquote>
<p><strong>회고</strong>
뒤에 NAND에서 컴퓨터까지의 내용은 읽기만 하고 정리하지는 않았다. 인공지능 수업의 진도와 얼추 비슷해서 정리하고 읽으면 수업에 도움이 될 것 같다. 수업에서는 XOR의 구현이 쉽지 않다는 것을 넘어서 활성화 함수와 다층 퍼셉트론까지 진도를 나가긴했지만...
<br> 외에도 back propagation 진도를 나가기 전에 책의 해당 부분을 읽어보려 했는데, 학기 중 생각보다 동아리나 다른 활동이 바빠서 쉽지 않을 것 같다....</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터베이스 2주차 내용 정리]]></title>
            <link>https://velog.io/@summeryoung_/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-2%EC%A3%BC%EC%B0%A8-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@summeryoung_/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-2%EC%A3%BC%EC%B0%A8-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sat, 14 Mar 2026 12:51:01 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="1-관계-데이터-모델의-개념">1. 관계 데이터 모델의 개념</h1>
</blockquote>
<h2 id="릴레이션의-개념">릴레이션의 개념</h2>
<h3 id="관계-데이터-모델의-기본-개념">관계 데이터 모델의 기본 개념</h3>
<p>개념적 구조를 논리적 구조로 표현하는 논리적 데이터 모델로 하나의 개체에 관한 데이터를 하나의 릴레이션에 저장.</p>
<h3 id="관계-relation">관계 (relation)</h3>
<p>릴레이션(테이블) 내의 관계와 릴레이션(테이블) 간의 관계가 존재.</p>
<ul>
<li><strong>릴레이션 내의 관계</strong>: 릴레이션 안에 있는 데이터들의 집합으로 표현.</li>
<li><strong>릴레이션 간의 관계</strong>: 릴레이션을 식별 가능한 값을 이용해 표현.</li>
</ul>
<p>예) 도서 릴레이션의 &#39;도서번호&#39;와 고객 릴레이션의 &#39;고객번호&#39;를 주문 릴레이션에 저장하여 관계를 표현</p>
<h2 id="릴레이션-스키마와-인스턴스">릴레이션 스키마와 인스턴스</h2>
<h3 id="릴레이션">릴레이션</h3>
<p>: 행과 열로 구성된 테이블. 하나의 개체에 관해 데이터를 2차원 테이블의 구조로 저장한 것.
<strong>스키마(schema)</strong>와 <strong>인스턴스</strong>로 이루어짐.</p>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/5a34b68b-6529-4f63-9a3a-e2dbad4dae6c/image.png" alt=""></p>
<h3 id="릴레이션-스키마">릴레이션 스키마</h3>
<p>: 릴레이션의 논리적 구조로, 릴레이션의 이름과 포함된 모든 속성의 이름으로 정의.</p>
<ul>
<li><strong>속성(attribute)</strong>: 릴레이션 스키마의 열</li>
<li><strong>도메인(domain)</strong>: 속성이 가질 수 있는 값의 집합</li>
<li><strong>차수(degree)</strong>: 속성의 개수</li>
<li>자주 변하지 않는 편</li>
<li>표기법: <code>릴레이션이름 (속성1, 속성2, 속성3 ...)</code> 
또는 <code>릴레이션이름(속성1: 도메인1, 속성2: 도메인2, ...)</code><br>

</li>
</ul>
<h3 id="릴레이션-인스턴스">릴레이션 인스턴스</h3>
<p>: 릴레이션 스키마에 실제로 저장된 데이터 집합</p>
<ul>
<li><strong>튜플(tuple)</strong>: 릴레이션의 행</li>
<li><strong>카디날리티(cardinality)</strong>: 튜플의 수</li>
<li>삽입/추가/삭제/수정이 자주 발생함 (동적)</li>
</ul>
<h3 id="릴레이션의-특징">릴레이션의 특징</h3>
<table>
<thead>
<tr>
<th align="center">특징</th>
<th align="center">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="center">속성의 <strong>원자성</strong></td>
<td align="center">- 속성은 <strong>원자값(단일값)</strong>만을 가짐<br>- 여러 값을 넣을 때에는 테이블을 분리하거나 속성을 나눠야함</td>
</tr>
<tr>
<td align="center">속성의 <strong>무순서성</strong></td>
<td align="center">- 하나의 릴레이션에서 <strong>속성 사이의 순서는 무의미함</strong></td>
</tr>
<tr>
<td align="center">속성의 <strong>동일성</strong></td>
<td align="center">- 각 속성은 정의도니 도메인에 속하는 동일한 유형의 값만을 가짐</td>
</tr>
<tr>
<td align="center">튜플의 <strong>유일성</strong></td>
<td align="center">- 하나의 릴레이션에는 동일한 튜플이 존재할 수 없음 (중복저장X)</td>
</tr>
<tr>
<td align="center">튜플의 <strong>무순서성</strong></td>
<td align="center">- 하나의 릴레이션에서 튜플 사이의 순서는 무의미함</td>
</tr>
</tbody></table>
<h3 id="관계-데이터-모델">관계 데이터 모델</h3>
<p>데이터를 2차원 테이블인 릴레이션으로 표현함. 릴레이션에 대한 <strong>제약 조건과 관계연산</strong>을 위해 <strong>관계대수</strong>를 정의함</p>
<p><strong>관계 데이터베이스 시스템</strong>은 관계 데이터 모델을 컴퓨터 시스템에 구현한 것으로 관계 데이터 모델에 기초해 SQL을 기반으로 구현하였다.</p>
<hr>
<blockquote>
<h1 id="2--무결성-제약조건">2.  무결성 제약조건</h1>
</blockquote>
<h2 id="키-key">키 (key)</h2>
<h3 id="정의">정의</h3>
<p><strong>키(key)</strong>: 릴레이션에서 특정 <strong>튜플들을 유일하게 구별하는 속성 또는 속성들의 집합</strong>. 키가 되는 속성은 <strong>반드시 값이 달라서 튜플들을 서로 구별</strong>할 수 있어야함.</p>
<p>키의 특성으로는 아래와 같은 것들이 있고, 예로는 <strong>기본키, 대체키, 외래키, 슈퍼키, 후보키</strong>를 들 수 있다.</p>
<ul>
<li><strong>유일성</strong>: 하나의 릴레이션에서 모든 튜플은 서로 다른 키값을 가져야함.</li>
<li><strong>최소성</strong>: 꼭 필요한 최소한의 속성들로만 키를 구성.</li>
</ul>
<h3 id="키의-종류">키의 종류</h3>
<table>
<thead>
<tr>
<th align="center">키의 종류</th>
<th align="center">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><strong>슈퍼키</strong></td>
<td align="center">- <strong>유일성</strong>을 만족하는 속성/속성들의 집합</td>
</tr>
<tr>
<td align="center"><strong>후보키</strong></td>
<td align="center">- <strong>유일성과 최소성</strong>을 만족하는 속성/속성들의 집합<br>- 슈퍼키 중에서 불필요한 속성을 제거한 키</td>
</tr>
<tr>
<td align="center"><strong>기본키</strong></td>
<td align="center">- 후보키 중에서 기본적으로 사용하기 위해 선택한 키<br>- <strong>NULL을 허용하지 않음</strong><br>- 밑줄을 그어서 표시</td>
</tr>
<tr>
<td align="center"><strong>대체키</strong></td>
<td align="center">- 기본키로 선택되지 못한 후보키</td>
</tr>
<tr>
<td align="center"><strong>대리키</strong></td>
<td align="center">- 기본키가 보안을 필요로 하거나 여러 속성으로 구성되어 복잡하거나, <br>마땅한 기본키가 없을 때, 일련번호 같은 가상의 속성을 만들어 기본키로 삼음 <br> - DBMS나 소프트웨어가 임의로 생성함</td>
</tr>
</tbody></table>
<img src="https://velog.velcdn.com/images/summeryoung_/post/847d20e2-27a0-40f1-aadb-7b89f28b01bc/image.png" width=70%>

<h3 id="외래키">외래키</h3>
<ul>
<li><p><strong>다른 릴레이션의 기본키를 참조하는 속성</strong> 또는 속성들의 집합.</p>
</li>
<li><p><strong>NULL값과 중복값이 가능</strong></p>
</li>
<li><p>릴레이션들 사이의 관계를 표현</p>
<ul>
<li>참조하는 릴레이션: 외래키를 가진 릴레이션</li>
<li>참조되는 릴레이션: 외래키가 참조하는 기본키를 가진 릴레이션</li>
</ul>
</li>
<li><p>외래키 속성과 그것이 참조하는 기본키 속성의 <strong>이름이 달라도 되지만, 도메인은 같아야함</strong>. 
(즉, INTEGER, VARCHAR 이런 도메인은 같아야함)</p>
</li>
<li><p>외래키를 <strong>가진 릴레이션을 자식</strong>, 외래키가 <strong>참조하는 대상 릴레이션을 부모</strong> 릴레이션이라고도함.</p>
</li>
<li><p><strong>참조되는(기본키)값이 변경될 경우 참조하는(외래키) 값도 변경</strong>됨</p>
</li>
<li><p>외래키는 기본키의 일부가 될 수 있음</p>
</li>
<li><p>같은 릴레이션의 기본키를 참조하는 외래키 역시 정의할 수 있음.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/4e6ecfa1-02ec-4b55-b666-70b99308df77/image.png" alt=""></p>
<h2 id="무결성-제약조건">무결성 제약조건</h2>
<p><strong>무결성</strong>: 데이터에 결함이 없는 상태를 말한다. 즉, 데이터가 <strong>정확하고 유효하게 유지된 상태</strong>.</p>
<p><strong>데이터의 무결성</strong>: 데이터의 무결성을 보장하고 일관되고 정확한 상태로 유지하기 위한 규칙으로 일관성과 정확성을 바탕으로 구축된 데이터베이스가 계속 무결성을 유지하기 위해서는 <strong>튜플의 삽입/삭제/수정 시</strong> 데이터의 <strong>제약조건 준수 여부</strong>를 확인해야함.</p>
<h3 id="⭐--무결성-제약조건">⭐  무결성 제약조건</h3>
<ol>
<li><strong>도메인 무결성 제약조건</strong></li>
</ol>
<p><strong>도메인 제약</strong>: 릴레이션 내 <strong>튜플들이 각 속성의 도메인에 지정된 값</strong>만 가져야한다는 조건
<br> 위반 예) INT가 도메인인 애트리뷰트인데, 특정 튜플에서 VARCHAR.</p>
<ol start="2">
<li><strong>개체 무결성 제약조건</strong></li>
</ol>
<p><strong>기본키 제약</strong>: 기본키는 <strong>NULL 값을 가져서는 안되며</strong> 릴레이션 내에서 <strong>오직 하나의 값만</strong> 존재해야하는 것을 지켜야하는 조건.
<br> 위반 예) 기본키가 NULL이거나 중복인 경우, 복합키로 만든 기본키의 일부가 NULL, 기본키를 가진 행 삭제 시 -&gt; 해당 기본키를 참조하는 외래키가 존재할 때</p>
<ol start="3">
<li><strong>참조 무결성 제약조건</strong></li>
</ol>
<p><strong>외래키 제약</strong>: 릴레이션 간의 참조관계를 선언하는 제약조건으로, 자식 릴레이션의 외래키는 부모 릴레이션의 기본키와 도메인이 같아야하며, <strong>자식 릴레이션의 값이 변경될 때 부모 릴레이션의 제약을 받음. (부모에 없는 값을 자식이 참조하면 위반)</strong> 다만, 외래키 속성이 널값을 가질 수 는 있다.
<br> 위반 예) 참조 중인 부모 키 삭제/변경, 존재하지 않는 부모 키 참조</p>
<h3 id="무결성-제약-조건의-수행">무결성 제약 조건의 수행</h3>
<p>부모 릴레이션에서 튜플을 삭제할 때 참조 무결성 제약 조건을 수행하기 위한 옵션</p>
<ul>
<li><strong>RESTRICTION/NO ACTION</strong>: 삭제 거부 (기본)</li>
<li><strong>CASCADE</strong>: 부모 삭제 시 자식 역시 함께 삭제</li>
<li><strong>SET NULL / SET DEFAULT</strong>: 부모 삭제 시 자식의 외래키를 NULL 또는 기본값으로 설정</li>
</ul>
<img src="https://velog.velcdn.com/images/summeryoung_/post/4e11d801-bbb2-47a7-a1a7-84b45293a808/image.png" width=80%>

<hr>
<blockquote>
<h1 id="3-관계대수">3. 관계대수</h1>
</blockquote>
<h3 id="관계대수">관계대수</h3>
<p>릴레이션에서 원하는 결과를 얻기 위해 릴레이션의 처리 과정을 순서대로 기술하는 절차적 언어. </p>
<p><strong>기본 연산자 5개(셀렉션, 프로젝션, 합집합, 차집합, 카티션 프로덕트)</strong>. 일반 집합 연산자와 순수 관계 연산자로 분류.</p>
<p>피연산자와 연산의 결과 모두 릴레이션인 <strong>폐쇄 특성</strong>이 있음.</p>
<h2 id="집합-연산">집합 연산</h2>
<p><strong>일반 집합 연산자</strong>: 릴레이션이 <strong>튜플의 집합</strong>이라는 개념 이용
합집합, 교집합, 차집합은 피연산자인 두 릴레이션이 합병 가능해야 할 수 있음.</p>
<p>합병 가능 조건: </p>
<ul>
<li>두 릴레이션의 <strong>차수(=열의 수)</strong>가 같아야함</li>
<li>두 릴레이션에서 <strong>서로 대응되는 속성의 도메인</strong>이 같아야함</li>
</ul>
<h3 id="합집합-r∪s">합집합 (R∪S)</h3>
<p>:합병 가능한 두 릴레이션 R과 S의 합집합 </p>
<img src="https://velog.velcdn.com/images/summeryoung_/post/8a377b10-e41f-4d44-87c3-8be6beca6818/image.png" width=20%>

<img src="https://velog.velcdn.com/images/summeryoung_/post/67cb8121-16c7-4455-8e09-eba0769ba41f/image.png" width=50%>

<ul>
<li><p>결과 릴레이션의 특징:</p>
<ul>
<li><strong>차수는 릴레이션 R과 S의 차수와 동일</strong></li>
<li><strong>카디널리티(=행의 수)는 릴레이션 R과 S의 카디널리티를 더한 것과 같거나 적어짐</strong></li>
</ul>
</li>
<li><p>교환법칙 및 결합법칙이 성립됨</p>
</li>
</ul>
<br> 

<h3 id="교집합-r∩s">교집합 (R∩S)</h3>
<p>: 합병 가능한 두 릴레이션 R과 S의 교집합</p>
<img src="https://velog.velcdn.com/images/summeryoung_/post/ebfdc12f-db8b-464c-afd0-aff02afd28f4/image.png" width=20%>

<img src="https://velog.velcdn.com/images/summeryoung_/post/080ac640-fcf4-4a09-95c1-25e95fd658fd/image.png" width=50%>

<ul>
<li><p>결과 릴레이션의 특징</p>
<ul>
<li><strong>차수는 릴레이션 R과 S의 차수와 같음</strong></li>
<li><strong>카디널리티는 릴레이션 R과 S의 어떤 카디널리티보다 크지 않음(같거나 적음)</strong></li>
</ul>
</li>
<li><p>교환법칙과 결합법칙이 성립함</p>
<br>

</li>
</ul>
<h3 id="차집합-r-s">차집합 (R-S)</h3>
<p>: 합병 가능한 두 릴레이션 R과 S의 차집합</p>
<img src="https://velog.velcdn.com/images/summeryoung_/post/019ca8b5-0a2d-42ec-9365-d84d65bec746/image.png" width=40%>

<img src="https://velog.velcdn.com/images/summeryoung_/post/2a7fd7bb-3918-4f7d-bc58-60a61086e548/image.png" width=50%>

<ul>
<li><p>결과 릴레이션의 특징</p>
<ul>
<li><strong>차수는 릴레이션 R과 S의 차수와 같음</strong></li>
<li><strong>R-S의 카디널리티는 릴레이션 R의 카디널리티와 같거나 적음</strong></li>
<li><strong>S-R의 카디널리티는 릴레이션 S의 카디널리티와 같거나 적음</strong></li>
</ul>
</li>
<li><p>교환법칙, 결합법칙이 성립하지 않음</p>
</li>
</ul>
<br>

<h3 id="카티션-프로덕트-rxs">카티션 프로덕트 (RxS)</h3>
<p>: 릴레이션 R에 속한 각 튜플과 릴레이션 S에 속한 각 튜플을 <strong>모두 연결하여</strong> 만들어진 새로운 튜플을 반환</p>
<img src="https://velog.velcdn.com/images/summeryoung_/post/1525fad5-ccc0-43d8-a25e-7559c8dab4ff/image.png" width=20%>

<img src="https://velog.velcdn.com/images/summeryoung_/post/77fb7198-2cd7-4e71-aea6-ad02996463cd/image.png" width=70%>

<ul>
<li><p>결과 릴레이션의 특징</p>
<ul>
<li><strong>차수는 릴레이션 R과 S의 차수를 더한 것과 같음</strong></li>
<li><strong>카디널리티는 릴레이션 R과 S의 카디널리티를 곱한 것과 같음</strong></li>
</ul>
</li>
<li><p>교환법칙과 결합법칙이 성립함</p>
</li>
</ul>
<h2 id="셀렉션과-프로젝션">셀렉션과 프로젝션</h2>
<p><strong>순수 관계 연산자</strong>: 릴레이션의 구조와 특성을 이용하는 연산자.</p>
<img src="https://velog.velcdn.com/images/summeryoung_/post/4071afc1-4b9a-4915-8ccd-957da70c2558/image.png" width=90%>


<h3 id="셀렉션-σ">셀렉션 (σ)</h3>
<p>릴레이션에서 <strong>조건을 만족하는 튜플만 추출</strong>하기 위한 연산자</p>
<ul>
<li>수학적 표현: $σ_{조건식} (릴레이션)$</li>
<li>데이터 언어적 표현: <em>릴레이션</em> <strong>where</strong> <em>조건식</em></li>
</ul>
<blockquote>
<p><strong>조건식</strong>
비교식, 프레디킷이라고도함
<strong>속성과 상수의 비교나 속성들 간의 비교</strong>로 표현
비교연산자와 논리연산자를 이용해 작성
교환법칙 성립</p>
</blockquote>
<p>예) 고객 릴레이션에 등급이 gold이고, 적립금이 2000 이상인 튜플 검색: $σ<em>{등급=&quot;gold&quot;}(σ</em>{적립금 &gt;=2000} 고객
    ))$</p>
<h3 id="프로젝션">프로젝션</h3>
<p>릴레이션의 <strong>속성을 추출</strong>하기 위한 단항 연산자</p>
<ul>
<li>수학적 표현: $π_{속성 리스트}(릴레이션)$</li>
<li>데이터 언어적 표현: <em>릴레이션</em> [<em>속성리스트</em> ]</li>
</ul>
<p>예) 고객 릴레이션에서 적립금 검색: $π_{적립금}(고객)$</p>
<h2 id="조인-join">조인 (join)</h2>
<p><strong>두 릴레이션의 공통 속성</strong>을 기준으로 속성값이 <strong>같은 튜플을 수평으로 결합하는 연산</strong>으로 기본 연산자의 조합으로 구현할 수도 있는 유도된 연산자.</p>
<p>표현은 <strong><em>릴레이션1</em> ⋈ <em>릴레이션2</em></strong>으로 하며 <strong>기본 조인연산</strong>과 확장된 조인연산으로 구분됨.</p>
<table>
<thead>
<tr>
<th align="center">기본 조인 연산 종류</th>
<th align="center">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><strong>세타 조인($⋈_{\theta}$)</strong></td>
<td align="center">두 릴레이션간의 <strong>속성값</strong>을 비교하여 조건에 맞는 튜플을 반환</td>
</tr>
<tr>
<td align="center"><strong>동등 조인($⋈_{R=S}$)</strong></td>
<td align="center">세타조인에서 <strong>=</strong> 연산자를 사용한 조인으로 보통 조인을 말하면 이쪽.</td>
</tr>
<tr>
<td align="center"><strong>자연 조인($⋈_{N}$)</strong></td>
<td align="center">동등조인과 동일하지만 <strong>중복 속성을 제거</strong>하고 반환.</td>
</tr>
</tbody></table>
<h3 id="동등조인">동등조인</h3>
<p>세타조인 중에서 비교연산자가 <strong>&#39;=&#39;</strong>인 경우로 가장 많이  사용되며, 양쪽 테이블의 공통 컬럼이 모두 결과에 나타남.</p>
<p>예) 고객이 주문한 사항을 모두 나타내기 위해서는 <strong>고객.고객번호 = 주문.고객번호</strong>로 동등조인 $⋈_{고객.고객아이다=주문.고객아이디}$</p>
<h3 id="세타조인">세타조인</h3>
<p>조인 조건에 비교연산자를 자유롭게 사용하며 양쪽 테이블의 공통 컬럼이 모두 결과에 나타남.</p>
<p>예) 나이 25세 이상인 고객이 주문한 사항을 모두 나타내기 위해서는 <strong>고객.고객아이디 = 주문. 고객아이디 ^ 고객.나이 &gt; 25</strong> 주문.</p>
<h3 id="자연조인">자연조인</h3>
<p>동등조건에서 <strong>중복되는 조인 칼럼을 하나 제거</strong>하여 더 깔끔하게 보여주는 조인</p>
<img src="https://velog.velcdn.com/images/summeryoung_/post/4e589828-358f-4574-94c1-27fae8dff21b/image.png" width=80%>

<h3 id="외부조인">외부조인</h3>
<p>자연조인 연산을 확장한 관계 대수 연산자로 <strong>자연조인에서 조인 조건에 맞지 않아 제외되는 튜플도 버리지 않고 NULL을 채워 반환함.</strong></p>
<table>
<thead>
<tr>
<th align="center">기본 조인 연산 종류</th>
<th align="center">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><strong>왼쪽 외부 조인(⟕)</strong></td>
<td align="center">왼쪽에 있는 릴레이션1에 존재하는 모든 튜플을 결과 릴레이션에 포함시킴</td>
</tr>
<tr>
<td align="center"><strong>오른쪽 외부 조인(⟖,)</strong></td>
<td align="center">오른쪽에 있는 릴레이션1에 존재하는 모든 튜플을 결과 릴레이션에 포함시킴</td>
</tr>
<tr>
<td align="center"><strong>완전 외부 조인(⟗)</strong></td>
<td align="center">양쪽에 존재하는 모든 튜플을 결과 릴레이션에 포함시킴</td>
</tr>
</tbody></table>
<h3 id="세미조인-⋉">세미조인 (⋉)</h3>
<p>자연조인 후 두 릴레이션 중 한 쪽 릴레이션의 결과만 반환.</p>
<p><strong>릴레이션1 ⋉ 릴레이션2</strong>: 릴레이션2를 <strong>조인 속성으로 프로젝트 연산</strong>한 후, 릴레이션1에 <strong>자연 조인</strong>하여 결과 릴레이션을 구성</p>
<p>불필요한 속성을 <strong>미리 제거</strong>하여 조인 연산 비용을 줄이는 장점 존재. 교환법칙이 성립하지 않음.</p>
<p><span style="color:gray;font-size:80%">수업 때 이거 바로 이해하지 못해서? 기억하지 못해서, 후에 문제 풀이할 때 써먹지 못했는데, 지금 생각해보면 이거 사용해서 풀면 조인하고, 프로젝션하거나 할 필요없이 정말 더 효율적으로 처리할 수 있었을 것 같다. </span></p>
<h2 id="디비전division">디비전(Division)</h2>
<p>다른 연산들과 달리 <strong>릴레이션의 속성값의 집합</strong>으로 연산을 수행함.</p>
<p>조인을 수행한 후 릴레이션2의 모든 튜플과 관련있는 릴레이션1의 튜플을 추출하는 연산으로 사용. <strong>특정 조건을 모두 충족하는 대상을 찾을 때 사용</strong></p>
<ul>
<li><p>표현법: <em>릴레이션1</em> $\div$ <em>릴레이션2</em></p>
<ul>
<li>단, 릴레이션1이 릴레이션2의 <strong>모든 속성을 포함</strong>하고 있어야함</li>
<li><strong>도메인이 같아야한다는 의미</strong></li>
</ul>
</li>
</ul>
<img src="https://velog.velcdn.com/images/summeryoung_/post/47b0b32e-4d82-46a5-a57e-a9e573dc6908/image.png" width=70%>


<p><span style="color:gray;font-size:80%"> 이것도 수업에서 바로 이해 못해서 그렇지 왠지 디비전 썼으면 뒤에 풀었던 문제를 더 쉽게? 간단하게? 푸는 방법이 있었을 것 같다... </span></p>
<h3 id="관계대수와-sql-매핑">관계대수와 SQL 매핑</h3>
<table>
<thead>
<tr>
<th align="center">관계대수 연산</th>
<th align="center">SQL 키워드</th>
<th align="center">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="center">셀렉션</td>
<td align="center">WHERE</td>
<td align="center">조건에 맞는 <strong>행/튜플</strong>만 추출</td>
</tr>
<tr>
<td align="center">프로젝션</td>
<td align="center">SELECT</td>
<td align="center">필요한 <strong>열/애트리뷰트</strong>만 추출</td>
</tr>
<tr>
<td align="center">합집합</td>
<td align="center">UNION</td>
<td align="center">두 결과의 합 <br>(중복 자동 제거, 속성의 수가 동일해야함)</td>
</tr>
<tr>
<td align="center">차집합</td>
<td align="center">EXCEPT/NOT IN</td>
<td align="center">첫 번째 결과에서 두 번째 결과를 제외</td>
</tr>
<tr>
<td align="center">카티션 곱</td>
<td align="center">FROM R, S / CROSS JOIN</td>
<td align="center">두 테이블 모두 가능한 조합<br> ($N \times M$ 개의 행 생성)</td>
</tr>
<tr>
<td align="center">조인</td>
<td align="center">JOIN ~ ON</td>
<td align="center">특정 조건(주로 외래키)를 만족하는 행들만 결합</td>
</tr>
<tr>
<td align="center">디비전</td>
<td align="center">NOT EXISTS</td>
<td align="center"><strong>모든 조건</strong>을 만족하는 대상 검색</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[[자바 ORM 표준 JPA 프로그래밍] 1주차 스터디 (2)]]></title>
            <link>https://velog.io/@summeryoung_/%EC%9E%90%EB%B0%94-ORM-%ED%91%9C%EC%A4%80-JPA-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-1%EC%A3%BC%EC%B0%A8-%EC%8A%A4%ED%84%B0%EB%94%94-2</link>
            <guid>https://velog.io/@summeryoung_/%EC%9E%90%EB%B0%94-ORM-%ED%91%9C%EC%A4%80-JPA-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-1%EC%A3%BC%EC%B0%A8-%EC%8A%A4%ED%84%B0%EB%94%94-2</guid>
            <pubDate>Sat, 14 Mar 2026 05:57:48 GMT</pubDate>
            <description><![CDATA[<h1 id="2장-jpa-시작">2장. JPA 시작</h1>
<h3 id="h2-데이터베이스-설치">H2 데이터베이스 설치</h3>
<img src="https://velog.velcdn.com/images/summeryoung_/post/39894bf2-422c-489f-8721-0c825824a070/image.png" width=70%>

<img src=" https://velog.velcdn.com/images/summeryoung_/post/f3937eba-59a9-48e8-8e54-3a177b97f53c/image.png" width=70%>

<pre><code class="language-sql">CREATE TABLE MEMBER (
    ID VARCHAR(255) NOT NULL, --아이디(기본 키)
    NAME VARCHAR(255),             --이름
    AGE INTEGER NOT NULL,       --나이
    PRIMARY KEY(ID)
);</code></pre>
<p>SQL 입력 후 결과</p>
<img src="https://velog.velcdn.com/images/summeryoung_/post/db14af0e-971a-4362-aeeb-3eef5c1ebfc2/image.png" width=70%>

<hr>
<h3 id="실습">실습</h3>
<p>1.라이브러리와 프로젝트 구조</p>
<p>일단 인텔리제이 IDE를 사용하고 있어서 이클립스를 따로 깔지 않고 인텔리제이에서 실습을 진행하였다.</p>
<img src="https://velog.velcdn.com/images/summeryoung_/post/18831ccb-dc4f-43ed-8cf4-5c34d295f00c/image.png" width=70%>

<p>프로젝트 구조는 아래와 같았는데, JpaMain.java가 실행 클래스이고, Member.java가 엔티티라고 한다. persistence.xml이 JPA 설정 정보가 담겨있는 파일이다.</p>
<p>JPA 구현체로 하이버네이트를 사용하기 위한 핵심 라이브러리는 아래가 있다고 한다.</p>
<ul>
<li>hibernate-core: 하이버네이트 라이브러리</li>
<li>hibernate-entitymanager: 하이버네이트가 JPA 구현체로 동작하도록 JPA표준을 구현한 라이브러리</li>
<li>hibernate-jpa-2.1-api: JPA2.1 표준 API를 모아둔 라이브러리.</li>
</ul>
<img src="https://velog.velcdn.com/images/summeryoung_/post/cb0bf5e3-782b-409f-b16e-9aacd47f7e9c/image.png" width=50%>

<p><img src="https://velog.velcdn.com/images/summeryoung_/post/c969e272-69da-4e19-af16-a337831ed434/image.png" alt=""></p>
<p>라이브러리는 메이븐을 사용해 관리한다. 메이븐은 라이브러리를 관리해주는 도구로 pom.xml에 사용할 라이브러리를 적어주면 라이브러리를 자동으로 내려받아서 관리해준다고 한다. dependencies 아래에 사용할 라이브러리들을 지정해주면 된다. <code>groupID + artifactID + version</code>을 적어주면 라이브러리(jar 파일)을 메이븐 공식 지정소에서 내려받아 라이브러리에 추가해준다고한다.
<span style="color:gray;font-size:80%"> 일전에 스프링부트 사용할 때도, 이런 .xml 파일 처럼 어떤 파일에 사용할 라이브러리들을 적으면 알아서 설치해주는 도구가 있었다. 아마 메이븐이 아니라 그레이들이었던걸로 기억한다. </span></p>
<h3 id="객체-매핑">객체 매핑</h3>
<p>앞에서 만든 회원(MEMBER) 테이블을 애플리케이션(자바)에서 사용해야하므로 해당 클래스를 작성해준다. -&gt; 예제 코드에 이미 작성이 되어있어 이걸 사용하고 코드의 매핑정보만 확인했다.</p>
<table>
<thead>
<tr>
<th align="center">매핑 정보</th>
<th align="center">회원 객체</th>
<th align="center">회원 테이블</th>
</tr>
</thead>
<tbody><tr>
<td align="center">클래스와 테이블</td>
<td align="center">Member</td>
<td align="center">MEMEBER</td>
</tr>
<tr>
<td align="center">기본키</td>
<td align="center">id</td>
<td align="center">ID</td>
</tr>
<tr>
<td align="center">필드와 컬럼</td>
<td align="center">username</td>
<td align="center">NAME</td>
</tr>
<tr>
<td align="center">필드와 컬럼</td>
<td align="center">age</td>
<td align="center">AGE</td>
</tr>
</tbody></table>
<p>여기에 각각 JPA에서 제공해주는 어노테이션을 추가한다.</p>
<pre><code class="language-java">@Entity
@Table(name=&quot;MEMBER&quot;)
public class Member {</code></pre>
<p><strong><code>@Entity</code></strong>: 해당 클래스를 <strong>테이블</strong>과 매핑한다고 JPA에 알려주며, 이런 클래스를 엔티티 클래스라고한다.
<strong><code>@Table</code></strong>: 엔티티 클래스에 매핑할 테이블의 정보를 알려줌. 위에서는 <code>name</code> 속성을 사용해 Member 엔티티를 MEMBER 테이블에 매핑하고 있다. 생략 시 클래스 이름을 테이블 이름으로 매핑한다.</p>
<pre><code class="language-java">@Id
    @Column(name = &quot;ID&quot;)
    private String id;

    @Column(name = &quot;NAME&quot;)
    private String username;</code></pre>
<p><strong><code>@Id</code></strong>: 엔티티 클래스의 필드를 테이블의 기본키에 매핑한다. 이런 필드를 <strong>식별자 필드</strong>라고 한다.
<strong><code>@Column</code></strong>: 필드를 컬럼(열)에 매핑한다. 위에서는 <code>name</code> 속성을 사용해 필드를 테이블의 컬럼에 매핑한다. 매핑 정보가 없는 필드의 경우 필드명을 사용해 컬럼명으로 매핑을 진행한다. 데이터베이스가 대소문자를 구분하지 않는다고 가정(구분을 위해서는 명시적인 매핑 필요)</p>
<h3 id="persistencexml-설정">persistence.xml 설정</h3>
<p>JPA의 설정 정보 관리 설정 파일로 해당 파일이 META-INF/persistence.xml 클래스 패스 경로에 있으면 별도 설정 없이 JPA가 인식 가능하다고한다.</p>
<ul>
<li><p><code>&lt;persistence xmlns=&quot;http://xmlns.jcp.org/xml/ns/persistence&quot; version=&quot;2.1&quot;&gt;</code>:
설정 파일은 persistence로 시작하며, 여기서 XML 네임스페이스와 사용할 버전을 지정한다.</p>
</li>
<li><p><code>&lt;persistence-unit name=&quot;jpabook&quot;&gt;</code>:
JPA 설정은 영속성 유닛에서부터 시작한다. 일반적으로는 연결할 데이터베이스 당 하나의 영속성 유닛을 등록한다. <strong>영속성 유닛에는 고유한 이름</strong>을 부여해야한다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/d539630c-8755-4189-9770-ec265319f734/image.png" alt=""></p>
<ul>
<li><p>JPA 표준 속성</p>
<ul>
<li><code>jdbc.driver</code>: JDBC 드라이버</li>
<li><code>jdbc.user</code>: 데이터베이스 접속 아이디</li>
<li><code>jdbc.password</code>: 데이터베이스 접속 비밀번호</li>
<li><code>jdbc.url</code>: 데이터베이스 접속 url<br></li>
</ul>
</li>
<li><p>하이버네이트 속성</p>
<ul>
<li><code>hibernate.dialect</code>: 데이터베이스 방언 설정</li>
</ul>
</li>
</ul>
<p><code>javax.persistence</code>로 시작하는 속성은 JPA 표준 속성으로 특정 구현체에 종속되지 않지만, <code>hibernate</code>로 시작하는 속성은 하이버네이트 전용 속성으로 하이버네이트에서만 사용 가능하다.</p>
<p><strong>데이터베이스 방언(dialect)</strong>은 SQL 표준을 지키지 않고 특정 데이터베이스 만의 고유한 기능을 가지는 것을 JPA에서 이르는 말이다. 애플리케이션 개발자가 특정 데이터베이스에 종속되는 기능을 많이 쓰면 후에 교체가 어렵기에 대부분의 JPA 구현체들은 다양한 데이터베이스 방언 클래스를 제공해 이런 문제를 해결한다고한다.</p>
<h3 id="애플리케이션-개발">애플리케이션 개발</h3>
<pre><code class="language-java">//엔티티매니저팩토리 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory(&quot;jpabook&quot;);
//엔티티매니저 생성
EntityManager em = emf.createEntityManager();
//트랜잭션 획득
EntityTransaction tx = em.getTransaction();

try {
    tx.begin(); //트랜잭션 시작
    logic(em); //비즈니스 로직 실행
    tx.commit();
} catch (Exception e) {
    tx.rollback(); //트랜잭션 롤백
} finally {
    em.close()
}

emf.close();
...

private static void logic (EntityManager em) {...}</code></pre>
<ol>
<li><strong>엔티티 매니저 팩토리</strong> 설정</li>
</ol>
<p><strong>JPA를 시작하기 위해서는 persistence.xml의 설정 정보를 사용해서 엔티티 매니저 팩토리를 생성</strong>해야한다. 이때 <code>Persistence</code> 클래스를 사용하는데, 이 클래스는 엔티티 매니저 팩토리를 생성해 JPA를 사용할 준비를 한다.
<br> META-INF/persistence.xml에서 이름이 jpabook인 영속성 유닛을 찾아 엔티티 매니저 팩토리를 생성한다고한다. 이때 <strong>persistence.xml의 설정 정보를 읽어 JPA 동작을 위한 기반 객체를 만들고 JPA 구현체에 따라 데이터베이스 커넥션 풀도 생성</strong>한다고 한다.
<br> 엔티티 매니저 팩토리 생성 비용은 크기 때문에 애플리케이션 전체에서 딱 한 번만 생성하고 공유해 사용해야한다.</p>
<br>

<ol start="2">
<li><p><strong>엔티티 매니저</strong> 생성
엔티티 매니저 팩토리에서 엔티티 매니저를 생성한다. JPA의 기능 대부분은 이 엔티티 매니저가 제공한다. 대표적으로 <strong>엔티티 매니저를 사용해서 엔티티를 데이터베이스에 등록/수정/삭제/조회할 수 있다.</strong> 
<br>엔티티 매니저는 내부에 데이터소스(커넥션)를 유지하면서 데이터베이스와 통신한다. 엔티티 매니저는 데이터베이스 커넥션과 밀접한 관계가 있기에 <strong>스레드간 공유하거나 재사용하면 안된다.</strong></p>
</li>
<li><p>종료
사용이 끝난 엔티티 매니저와 엔티티 매니저 팩토리는 종료해야한다.</p>
</li>
</ol>
<h3 id="트랜잭션-관리">트랜잭션 관리</h3>
<p><strong>JPA에서는 항상 트랜잭션 안에서 데이터를 변경해야한다.</strong> 트랜잭션 없이 데이터를 변경하면 예외가 발생한다. 트랜잭션을 시작하려면 <strong>엔티티 매니저에서 트랜잭션 API를 받아와야한다.</strong></p>
<h3 id="비즈니스-로직">비즈니스 로직</h3>
<p>비즈니스 로직에서는 회원 엔티티를 생성하고, 엔티티 매니저를 통해 데이터베이스에 <strong>등록, 수정, 삭제, 조회</strong>를 한다.</p>
<ol>
<li><p>등록
엔티티를 저장하기 위해서는 <strong><code>persist()</code> 메소드에 저장할 엔티티를 넘겨주면 된다</strong>. JPA는 해당 클래스(엔티티)의 매핑정보를 분석해 적절한 SQL을 만들어 데이터베이스에 전달한다. </p>
<pre><code class="language-java">String id = &quot;id1&quot;;
Member member = new Member();
member.setId(id);
member.setUsername(&quot;익명&quot;);
member.setAge(2);

//등록
em.persist(member);</code></pre>
</li>
<li><p>수정
1장의 설명에도 나왔지만, JPA에는 수정 메소드가 따로 존재하지 않는다고 한다. 클래스(엔티티)의 필드를 수정하면 이후 트랜잭션에서   변경사항을 업데이트한다. JPA는 어떤 엔티티가 변경되었는지 추적하는 기능을 가져서 가능한 일이다.</p>
<pre><code class="language-java">member.setAge(20);</code></pre>
</li>
<li><p>삭제
엔티티 삭제를 위해서는 <strong><code>remove()</code> 메소드에 삭제하려는 엔티티를 넘겨주면 된다</strong>. 이 경우 DELETE SQL을 생성해 JPA에서 처리하게된다.</p>
<pre><code class="language-java">em.remove(member);</code></pre>
</li>
<li><p>한 건 조회
<code>find()</code> 메소드는 조회할 엔티티 타입과 <code>@Id</code>로 데이터베이스의 테이블 기본키와 매핑한 식별자값으로 엔티티 하나를 조회하는 메소드이다.</p>
<pre><code class="language-java">Member findMember = em.find(Member.class, id);</code></pre>
</li>
</ol>
<h2 id="jpql">JPQL</h2>
<p>JPA는 엔티티 객체를 중심으로 개발하기 때문에 검색 시에도 테이블이 아닌 객체 대상의 검색이 필요하다. 하지만, 이를 위해서는 데이터베이스의 모든 데이터를 애플리케이션으로 불러와 엔티티 객체로 변경한 후에 검색해야하는데 이는 현실적으로 불가능하다.</p>
<p>따라서 검색 조건이 포함된 SQL의 사용이 필요한데 JPA에서는 JPQL이라는 쿼리 언어를 통해 이 문제를 해결한다.</p>
<p>JPA는 <strong>SQL을 추상화한 JPQL이라는 객체지향 쿼리 언어</strong>를 제공한다. SQL과 문법이 거의 유사해 SELECT, JOIN, FROM, WHERE, GROUP BY, HAVING 등 사용이 가능한데 차이점이 존재한다.</p>
<ul>
<li>JPQL은 <strong>엔티티 객체를 대상으로 쿼리</strong>를 한다.</li>
<li>SQL은 <strong>데이터베이스 테이블을 대상으로 쿼리</strong>를 한다.</li>
</ul>
<pre><code class="language-java">TypedQuery&lt;Member&gt; query = 
em.createQuery(&quot;select m from Member m&quot;, Member.class);

List&lt;Member&gt; members = query.ResultList();</code></pre>
<p>사용은 위의 코드와 같이할 수 있으며, <strong>JPQL은 데이터베이스 테이블은 전혀 알지 못한다.</strong> JPQL 사용을 위해서는 <code>em.createQuery(JPQL, 반환타입)</code> 메소드를 통해 쿼리 객체를 생성하고, 쿼리 객체의 <code>getResultList()</code> 메소드를 호출하면된다.</p>
<hr>
<p><strong>자바 ORM 표준 JPA 프로그래밍</strong> 책을 읽으며 정리하는 스터디 기록.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[자바 ORM 표준 JPA 프로그래밍] 1주차 스터디 (1)]]></title>
            <link>https://velog.io/@summeryoung_/EFUB-%EC%9E%90%EB%B0%94-ORM-%ED%91%9C%EC%A4%80-JPA-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@summeryoung_/EFUB-%EC%9E%90%EB%B0%94-ORM-%ED%91%9C%EC%A4%80-JPA-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sat, 14 Mar 2026 03:37:11 GMT</pubDate>
            <description><![CDATA[<h1 id="1장-jpa-소개">1장. JPA 소개</h1>
<p>JPA를 사용하면, <strong>CRUD SQL을 작성할 필요가 없고, 조회된 결과와 객체를 매핑하는 일도 자동으로 JPA에서 처리</strong>해준다는 장점이 있음. 또한 애플리케이션을 SQL이 아닌 객체 중심으로 개발할 수 있어 <strong>생산성과 유지보수</strong>가 좋아지고, <strong>테스트 작성</strong>이 편리해진다.</p>
<p><strong>JPA</strong>는 자바 진영에서 만든 <strong>ORM 기술 표준</strong>으로, ORM은 Object-Relational Mapping의 약자로 객체 지향 프로그래밍 언어의 객체(클래스)와 관계형 데이터베이스의 테이블을 자동으로 매핑해, SQL 쿼리 대신 코드 수준에서 데이터를 조작할 수 있게 해주는 기술을 말한다.
<span style="color:gray;font-size:80%"> 실제로 이전에 스프링에서 JPA를 찍먹했을 때도, SQL 쿼리를 하나도 몰랐는데 사용하는데 무리가 없었다.  </span></p>
<h2 id="11-sql을-직접-다룰-때의-문제">1.1 SQL을 직접 다룰 때의 문제</h2>
<p>데이터베이스에서 데이터를 관리하기 위해서는 SQL을 사용해야하는데 이때 자바로 작성한 애플리케이션은 <strong>JDBC API</strong>를 사용해 <strong>SQL</strong>을 데이터베이스에 전달하고 데이터베이스로 부터 필요한 데이터를 반환받는다.</p>
<p>다만, <strong>데이터베이스는 객체 구조와 달리 데이터 중심의 구조</strong>를 가지기 때문에 객체를 데이터베이스에 직접 저장하거나 조회할 수는 없다. 그렇기에 이 다른 구조에 맞게 각각 변환해주는 부분의 코드가 필요하다.</p>
<pre><code class="language-java">    ResultSet rs = stmt.executeQuery(sql); //SQL 쿼리 실행
    //아래처럼 쿼리로 가져온 데이터베이스의 정보를 
    //객체에 또 다시 저장해주기 위해 변환해서 객체로 만들어주는 과정이 필요
    String member = rs.getString(&quot;MEMBER_ID&quot;);
    String name = rs.getString(&quot;NAME&quot;);

    Member member = new Member(member, name);

    //등록할 때는 또 객체에서 각 데이터(또는 필드)를 가져와서 데이터베이스에 맞게
    //처리/저장?해주는 부분이 필요 (그리고 이런 부분이 귀찮고, 너무 빈번히 작성이 필요하다)
    String sql = &quot;INSERT INTO MEMBER(MEMBER_ID, NAME) VALUES (?,?)&quot;;

    pstmt.setString(1, member.getMemeberId());
    pstmt.setString(2, member.getName());
</code></pre>
<p><span style="color:gray;font-size:80%"> 이 부분의 단점은 데이터베이스가 정보가 저장되는 구조와 자바에서 사용하는 객체(클래스)의 구조가 다르다는 부분에서 발생한다는걸 새삼 책을 읽으며 느꼈다. 이전에 SQL을 사용해보았을 때도, JPA를 사용해 CRUD를 작성해본 것과 비교하면 중간에 데이터베이스에서 받아온 데이터를 속성 이름에 따라 다시 받아서 형태를 코드로 직접 변화하는 작업을 매번 수행해줘야했다. 그때 강의를 들으면서는 단순히 JPA 쓰면 코드도 안 쓰고 알아서 처리해주니 편하네?라는 생각을 했는데 지금 보니까 이런 구조가 다른 것들을 알아서 처리해주니까 생략할 수 있는 코드들이라는 생각이 든다.</span></p>
<p>이렇게 코드를 작성하게 된다면 <strong>SQL에 의존적으로 개발</strong>을 하게 된다. 즉, 고객/서비스의 수정 요청에 맞추어 코드를 수정해야할 때 수정하는 양이 굉장히 많게된다. 이럴 경우 확장성이나 재사용성 측면에서도 좋지 않다.</p>
<p>예로는 데이터베이스에서 관리하는 속성(애트리뷰트)가 하나 늘어났을 때, JPA를 사용하지 않고 있다고 가정하면,</p>
<blockquote>
<ol>
<li>객체 클래스의 필드 추가<ol start="2">
<li>데이터베이스 수정</li>
<li>객체-데이터베이스 연결고리 모두 수정 (즉, SQL문을 사용했던 CRUD 관련 부분: 조회/수정/생성 등을 전부 수정해야한다)</li>
</ol>
</li>
</ol>
</blockquote>
<p>아래 작업을 필수적으로 수행해야한다.</p>
<p>외에도 만약 일반적인 조회가 아니라 무언가를 기준으로 조회하거나 객체가 다른 객체와 관계를 맺는 등 실제 서비스에서는 더 다양한 목적/서비스를 위한 기능들이 존재하는걸 고려하면 이런 의존적인 개발은 비효율적이고 피로하다. 수정 과정에서 오류가 발생할 여지가 많고, 관련된 코드들이 체인처럼 연결되어 그 각각을 수정하는 데에 피로와 오류 발생 가능성이 늘어난다. 오류 발생시 또 코드를 다 살펴봐야한다는 점에서 객체에서의 문제인지, DAO에서의 문제인지, SQL/데이터베이스의 문제인지를 각각 보는 피로함 역시 존재한다.</p>
<p><strong>엔티티(Entity)는 비즈니스 요구사항을 모델링한 객체</strong>를 이르는 말이다. 고객(Customer)나 팀(Team) 같이 말이다. 만약 SQL에 모든 것을 의존하게 되면 개발자들은 엔티티를 신뢰하고 사용할 수 없고, DAO를 살펴보며 어떤 SQL이 실행되는지와 어떤 객체들이 함께 조회되는지 확인해야한다. </p>
<p>문제점을 정리하면 <strong>진정한 의미의 계층 분할의 어려움, 엔티티의 신뢰가 어려움, SQL에 의존적인 개발</strong>이라는 문제점이 존재한다.</p>
<hr>
<p>JPA를 사용하면 객체를 데이터베이스에 저장하거나 관리할 때, 직접 SQL문을 작성하지 않고 JPA에서 제공하는 API를 사용하면 되기에 이런 문제들이 줄어들고 해결된다. JPA를 사용해 CRUD API를 어떻게 짜는지 확인해보면 그 차이가 뚜렷하게 보인다.</p>
<ol>
<li><p>저장</p>
<pre><code class="language-java">jpa.persist(member); //저장</code></pre>
<p><code>persist()</code> 메소드를 통해 객체를 데이터베이스에 저장하면, JPA가 객체와 매핑정보를 보고 적절한 INSERT SQL을 생성해 DB에 전달.</p>
</li>
<li><p>조회</p>
<pre><code class="language-java">String memeberID = &quot;idString&quot;
Member member = jpa.find(Member.class, memberID); //조회</code></pre>
<p><code>find()</code> 메소드는 객체 하나를 DB에서 조회하는데, 이때 JPA는 객체와 매핑정보를 보고 적절한 SELECT SQL을 생성해 DB에 전달하고 그 <strong>결과로 Member 객체를 생성해 반환.</strong></p>
</li>
<li><p>수정</p>
<pre><code class="language-java">Member member = jpa.find(Member.class, memberId);
member.setName(&quot;변경이름&quot;); //수정</code></pre>
<p>JPA에서 별도로 수정 메소드를 제공하지는 않지만, 객체를 조회해 값을 변경하면 트랜잭션을 커밋할 때 DB에 적절한 UPDATE SQL이 전달된다.</p>
</li>
<li><p>연관된 객체 조회</p>
<pre><code class="language-java">Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam(); //연관 객체 조회</code></pre>
</li>
</ol>
<p><span style="color:gray;font-size:80%">코드를 살펴보면 정말 SQL문을 작성하지 않고, 자바 언어에서 만든 객체에서 getter, setter를 사용하듯이 사용하면, 데이터베이스에 알아서 저장/수정이 되며 조회 역시도 JPA에서 알아서 객체 형태로 반환해준다. </span></p>
<h2 id="12-패러다임의-불일치">1.2 패러다임의 불일치</h2>
<p>객체 지향에서는 부모 객체를 상속받거나, 다른 객체를 참조할 수 있는데에 반해, 데이터베이스는 데이터를 중심으로 구조화되어있기에 이런 부분의 차이가 존재한다. </p>
<p>이런 객체 지향 프로그래밍과 데이터베이스의 목표가 다른데에서 오는 차이를 <strong>패러다임의 불일치</strong>라고 하며, 이 두 불일치를 개발자가 중간에서 해결해야하는 문제가 생긴다.</p>
<h3 id="1-상속">1. 상속</h3>
<p>예시로 객체지향 프로그래밍의 상속을 구현하기 위해서는 데이터베이스 모델링에서는 슈퍼타입 서브타입 관계를 사용해야 유사하게 설계가 가능하다. </p>
<p>ITEM이라는 클래스를 상속받아 ALBUM이라는 클래스가 있다고 하면, ITEM 데이터베이스의 컬럼으로 ITEM_ID(PK)와 DTYPE을 가지고 있어야하며, DTYPE 컬럼을 통해 어떤 자식 테이블관의 <strong>관계</strong>가 있는지 정의해야한다. 예를 들면 DTYPE이 ALBUM이런식으로.</p>
<p>하지만 이 경우네는 조회나 추가 등의 작업을 할 때 이 둘을 연결해서 추가/조회하는 코드들이 무수히 많이 필요하다.</p>
<pre><code class="language-java">abstract class Item {
    Long id;
    String name;
}

class Album extends Item {
    String artist;
}</code></pre>
<p>위와 같은 상속관계에서 Album 객체를 저장하기 위해서는 이 객체를 분해해 두  SQL을 만들어야한다.</p>
<pre><code class="language-sql">INSERT INTO ITEM
INSERT INTO ALBUM</code></pre>
<p>이렇게 되면 부모 객체에서 부모 데이터만 꺼내 ITEM용 INSERT SQL문을 작성하고, 자식 객체에서 자식 데이터만 꺼내서 ALBUM용 INSERT SQL을 작성하고, 자식의 타입에 따라 DTYPE도 정해서 넣어주어야한다.</p>
<blockquote>
<h3 id="jpa에서의-상속-br"><strong>JPA에서의 상속</strong> <br></h3>
<p>JPA에서는 이런 불일치 문제를 대신 해결해, 그저 <strong>자바에서 객체를 저장하듯이 저장하JPA에서 알아서 두 SQL을 실행하여 위의 예시처럼 저장</strong>시켜준다. 조회의 경우에도 역시 JPA에서 알아서 두 테이블을 <strong>조인</strong>해 필요한 데이터를 조회해 그 결과를 반환해준다.</p>
</blockquote>
<h3 id="2-연관관계">2. 연관관계</h3>
<p>자바에서 객체가 다른 객체를 참조하는 일이 빈번한데, 이 부분은 그와 관련된 구조의 차이이다. 자바가 <strong>참조를 통해 접근해서 연관된 객체를 조회</strong>하는 반면, 데이터베이스는 <strong>외래키를 사용해 연관관계를 가지고, 조인을 사용해 연관 테이블을 조회</strong>하는 차이에서 오는 문제이다.</p>
<p>예시로 Member 객체 안에 Team이라는 객체를 참조해 가지고 있다고 하면, 이 참조 필드를 통해 Team이라는 객체가 가진 정보들에 쉽게 접근할 수 있다. 하지만 데이터베이스에서는 Member 테이블에서 MEMBER.TEAM_ID라는 외래키 컬럼을 통해 TEAM 테이블과 관계를 맺고, 외래키를 사용해서 조인을 한 후에야 테이블을 조회할 수 있다. </p>
<p>또한 외래키를 가질 경우에는 하나만으로도 서로서로 조회, 즉 양방향으로도 조인을 할 수 있지만, 객체는 반대로 한 방향의 조회만 가능하다. 이런 불일치를 개발자가 중간에서 변환해주어야하는데, JPA에서 이런 변환하는 노고를 줄여준다.</p>
<blockquote>
<h3 id="jpa에서의-연관관계"><strong>JPA에서의 연관관계</strong></h3>
<p>상속과 마찬가지로 연관관계 역시 JPA에서 알아서 문제를 해결해준다. 참조와 외래키 사이를 서로 적절히 변환하여 SQL문을 작성해 데이터베이스로 전달하거나, 객체 조회 시에도 외래키를 적절히 참조로 변환해 준다.</p>
</blockquote>
<h3 id="3-객체-그래프-탐색">3. 객체 그래프 탐색</h3>
<img src="https://velog.velcdn.com/images/summeryoung_/post/c1771840-4bfb-4da5-9f02-c2fc3bc87614/image.png" width=50%>

<p><span style="color:gray;font-size:80%">객체 간의 연관관계는 하나가 아니라 다양하고 복잡하게 연결되어있을 수 있다. 예를 들면 위의 그림처럼. 실제 자바에서 코드를 짜다보면 <code>character.getWeapon().getName();</code>과 같이 한 번의 참조가 아니라, 참조한 객체에서의 참조 대상까지 가는 경우가 빈번했었다. 이런 경우는 데이터베이스의 외래키의 외래키같은 참조와 동일한 식으로의 처리는 어려울 것이다.</span></p>
<p>SQL은 직접 다룰 때 처음 어떤 SQL을 실행하느냐에 따라 객체 그래프를 어디까지 탐색할 수 있을지 정해진다. 그렇기에 어디까지 그래프 탐색이 가능한지 알기 위해 DAO를 통해 SQL을 확인해야하는 의존적인 부분이 존재한다. 따라서 DAO에 관련 조회 메소드를 상황에 따라 여러개 만들어야하는 문제가 생긴다.</p>
<blockquote>
<h3 id="jpa에서의-객체-그래프-탐색"><strong>JPA에서의 객체 그래프 탐색</strong></h3>
<p>JPA에서는 <strong>연관 객체를 사용하는 시점에 적절한 SELECT SQL을 실행</strong>한다. 즉, 이를 통해 연관된 객체를 신뢰하고 객체 지향에서 사용하듯이 조회해 사용할 수 있다. 이 기능은 실제 객체를 사용하는 시점까지 데이터베이스 조회를 미룬다고해 <strong>지연로딩</strong>이라고 한다.</p>
</blockquote>
<h3 id="4-비교">4. 비교</h3>
<p>데이터베이스는 기본키의 값으로 행을 비교하는 반면 객체는 동일성과 동등성 비교 두 가지가 존재한다.</p>
<ul>
<li>동일성: <code>==</code> 비교. 객체 인스턴스의 <strong>주소</strong>를 비교</li>
<li>동등성: <code>.equals()</code>비교. 객체 내부의 <strong>값</strong>을 비교</li>
</ul>
<p>이런 차이로 만약에 데이터베이스의 같은 행을 다른 객체 변수에 저장하게 되면, 데이터베이스에서는 같은 값인 것이, 객체 프로그래밍에서는 서로 다른 동일성 비교의 결과를 내게된다. 이런 차이 극복을 위해 데이터베이스의 같은 행을 조회할 때마다 같은 인스턴스를 반환하도록 구현하는 것은 하지만 쉽지 않다.</p>
<blockquote>
<h3 id="jpa에서의-비교"><strong>JPA에서의 비교</strong></h3>
<p>JPA에서는 따라서 <strong>같은 트랜잭션일 때 같은 객체가 조회되는 것을 보장</strong>한다.</p>
</blockquote>
<hr>
<h1 id="13-jpa란">1.3 JPA란?</h1>
<p>앞에서 말했듯 <strong>JPA</strong>는 자바 진영에서 만든 <strong>ORM 기술 표준</strong>으로, 자바 ORM 기술에 대한 API 표준 명세서다. ORM은 <strong>객체와 관계형 데이터베이스를 매핑</strong>해준다는 의미라고 할 수 있다. JPA는 애플리케이션과 JDBC 사이에서 동작하게된다.</p>
<p>앞에서 언급된 패러다임 차이나 무수히 많은 SQL문을 적는 것을 ORM 프레임워크에서 대체해주기에, 객체 측면에서 정교한 객체 모델링이 가능하고, 관계형 데이터베이스 측에서는 데이터베이스에 맞는 모델링을 할 수가 있다.</p>
<p>JPA를 사용하기 위해서는 JPA를 구현한 ORM 프레임워크를 선택해야한다. JPA는 기술에 대한 표준 명세서니 말이다.</p>
<p>JPA를 사용해야하는 이유를 정리해보면 아래와 같다.</p>
<ul>
<li><strong>생산성</strong>: CRUD용 SQL을 직접 작성하지 않아도 되며, CREATE TABLE 같은 DDL문도 자동으로 생성해준다. -&gt; <strong>데이터베이스 설계 중심의 패러다임을 객체 설계 중심으로 역전 가능</strong></li>
<li><strong>유지보수</strong>: SQL을 직접 다루면 생기는 SQL에 의존적인 개발을 피해 수정/추가 시 유지보수해야할 코드의 양이 줄어든다.</li>
<li>패러다임 불일치 해결</li>
<li>성능</li>
<li>데이터 접근 추상화와 벤더 독립성: 페이징 처리와 같이 데이터베이스마다 달라 사용법을 배워야하는 부분을 JPA에서 처리해, 애플리케이션의 데이터베이스를 바꾸는 것이 조금 자유로워짐.</li>
</ul>
<hr>
<p>자바 ORM 표준 JPA 프로그래밍 책을 읽으며 정리하는 스터디 기록.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[인프런 데이터베이스] 1주차 스터디]]></title>
            <link>https://velog.io/@summeryoung_/%EB%B0%B1%EC%97%94%EB%93%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@summeryoung_/%EB%B0%B1%EC%97%94%EB%93%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Thu, 12 Mar 2026 15:58:57 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>인프런 데이터베이스 강의 (섹션1-섹션3) 기록</strong></p>
</blockquote>
<img src="https://velog.velcdn.com/images/summeryoung_/post/1095c77f-2702-4c30-b15c-e3341949b60b/image.png" width=70%>

<hr>
<h2 id="데이터베이스">데이터베이스</h2>
<p>파일: 성능, 보안, 편의성에 한계 존재 -&gt; 이런 한계를 극복하기 위한 전문화 소프트웨어 = 데이터베이스 -&gt; 데이터를 안전하고 편하고 빠르게 보관 가능.</p>
<p><strong>데이터베이스</strong>: 성능, 보안, 편의성이 제한있는 파일과 달리 전문화된 소프트웨어</p>
<h3 id="데이터베이스의-입출력">데이터베이스의 입출력</h3>
<ul>
<li>입력(input) → <strong>C</strong>reate, <strong>U</strong>pdate, <strong>D</strong>elete</li>
<li>출력(output) → <strong>R</strong>ead</li>
</ul>
<h3 id="데이터베이스-종류">데이터베이스 종류</h3>
<ul>
<li><strong>관계형 데이터베이스</strong>: Oracle(유료, 비쌈, 공기업/대기업 등 규모 큰 곳), MySQL(오픈소스, 무료),</li>
<li><strong>NoSQL</strong>: MongoDB (관계형X)</li>
</ul>
<h3 id="관계형-데이터베이스">관계형 데이터베이스</h3>
<ul>
<li><p>데이터 표의 형태로 정리 가능하며 데이터를 안전하고 편리하게 검색/정렬 등이 가능함
  예) MySQL, Oracle, SQL Server, PostgreSQL, DB2</p>
</li>
<li><p>스프레드시트에서 표를 통해 할 수 있는 기능(필터/정렬) 등의 기능을 데이터베이스의 언어(SQL/코드 ) 등을 사용해 얻어낼 수 있음.</p>
</li>
</ul>
<h2 id="mysql">MySQL</h2>
<h3 id="설치-및-설정">설치 및 설정</h3>
<img src="https://velog.velcdn.com/images/summeryoung_/post/5de7f35f-8fd3-422d-a6a8-f6f3b5275211/image.png" width=70%>

<p><code>./mysql -uroot -p</code>:</p>
<ul>
<li>-uroot: root라는 이름의 사용자(user)</li>
<li>-p: password 입력</li>
</ul>
<img src="https://velog.velcdn.com/images/summeryoung_/post/ad4eaf16-bed0-4cea-9c4c-3d21f5a69ab8/image.png" width=60%>

<h3 id="mysql의-구조">MySQL의 구조</h3>
<ul>
<li>표: 데이터를 기록하는 최종적인 형태<ul>
<li>이런 표가 많아지면 이걸 정리하는 ‘디렉토리’같은 것이 필요 → 이것이 데이터베이스 (관련 있는 표들을 그룹핑(묶는) 것) = 스키마 (= 표들을 그룹핑할 때 사용하는 것)</li>
<li>여러 스키마(데이터베이스)들이 저장되는 곳 = 데이터베이스 서버</li>
</ul>
</li>
</ul>
<h3 id="mysql의-서버-접속">MySQL의 서버 접속</h3>
<ul>
<li><p>보안의 장점 → 데이터베이스는 자체의 보안 체제 존재 + 권한 기능 존재.</p>
<ul>
<li>권한: 사용자들마다 모든 테이블에 대해 읽기/쓰기 등의 권한 주거나, 차등적으로 권한을 주거나 할 수 있음</li>
<li></li>
</ul>
</li>
<li><p><code>./mysql -uroot -p</code></p>
<ul>
<li><p>-uroot: root라는 이름의 사용자로 mysql 접속/실행하겠다는 의미</p>
<p>  ⇒ 실제로 루트 사용자(관리자)는 모든 권한을 가지고 있는 사용자이기에 해당 사용자 계정으로 데이터베이스에 들어가 작업하는 것은 위험함.</p>
</li>
<li><p>이러면 데이터베이스 서버에 들어간 상태</p>
</li>
</ul>
</li>
</ul>
<h3 id="mysql-스키마의-사용">MySQL 스키마의 사용</h3>
<ul>
<li><p>목표: 서버에 들어가서 → 스키마 생성 → 그 안에 표 생성</p>
</li>
<li><p><code>CREATE DATABASE &lt;데이터베이스 이름&gt;;</code>: 데이터베이스를 생성</p>
  <img src="https://velog.velcdn.com/images/summeryoung_/post/c8c74fbd-0fae-4cac-9bca-ba81850ba1a2/image.png" width=70%></li>
<li><p><code>DROP DATABASE &lt;데이터베이스 이름&gt;;</code> : 데이터베이스를 삭제</p>
</li>
<li><p><code>SHOW DATABASES;</code>: 생성한 데이터베이스 확인 (보여주기)</p>
  <img src="https://velog.velcdn.com/images/summeryoung_/post/84ae76f8-9719-4604-912a-30e6d593e296/image.png" width=50%></li>
<li><p><code>USE &lt;데이터베이스 이름&gt;;</code>: 데이터베이스를 사용하겠다는 의미. 해당 코드 적은 후의 명령들은 이 데이터베이스를 기준으로 실행함.</p>
  <img src="https://velog.velcdn.com/images/summeryoung_/post/1bb856b5-8e85-4a17-b01f-ea07cb504816/image.png" width=60%>

</li>
</ul>
<h3 id="sql과-테이블-구조">SQL과 테이블 구조</h3>
<ol>
<li><strong>SQL</strong> (Structured Query Language)</li>
</ol>
<ul>
<li>MySQL Server가 알아들을 수 있는 언어라고 생각할 수 있음.</li>
<li>관계형 데이터베이스 제품들이 공통적으로 데이터베이스 서버를 제어할 때 사용하는 언어.</li>
</ul>
<ol start="2">
<li>용어 정리</li>
</ol>
<ul>
<li><strong>테이블(table): 표</strong></li>
<li><strong>행(row, record)</strong></li>
<li><strong>열(column)</strong></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Altu-Bitu/알고리즘] 1주차 정렬, 맵, 셋 강의]]></title>
            <link>https://velog.io/@summeryoung_/Altu-Bitu%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-1%EC%A3%BC%EC%B0%A8-%EC%A0%95%EB%A0%AC-%EB%A7%B5-%EC%85%8B-%EA%B0%95%EC%9D%98</link>
            <guid>https://velog.io/@summeryoung_/Altu-Bitu%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-1%EC%A3%BC%EC%B0%A8-%EC%A0%95%EB%A0%AC-%EB%A7%B5-%EC%85%8B-%EA%B0%95%EC%9D%98</guid>
            <pubDate>Tue, 10 Mar 2026 09:36:34 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>*<em>26.03.09(월) 26.03.12(목)- *</em> 기록</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/448f6ff5-f0e4-44d3-8bc4-691d81df4180/image.png" alt=""></p>
<h2 id="버블-정렬bubble-sort">버블 정렬(bubble sort)</h2>
<ul>
<li>인접한 두 원소를 비교하는 정렬</li>
<li>(왼쪽 원소) &gt; (오른쪽 원소)일 때 <code>swap</code>
=&gt; 즉, v[i] &gt; v[i+1]이면 swap</li>
<li>가장 큰 원소부터 오른쪽에 정렬되기에, 데이터가 하나씩 정렬되면서 비교에서 제외</li>
<li><strong>시간 복잡도: O(n^2)</strong></li>
</ul>
<h3 id="boj-2750-수-정렬하기">BOJ 2750: 수 정렬하기</h3>
<pre><code class="language-c++">#include &lt;iostream&gt;
using namespace std;

int main() {

    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int n;
    cin &gt;&gt; n;

    int arr[n];
    for (int i=0; i&lt;n; i++) {
        cin &gt;&gt; arr[i];
    }

    for (int i=0; i&lt;n; i++) {
        for (int j=0; j&lt;n-1; j++) {
            if (arr[j] &gt; arr[j+1]) swap(arr[j], arr[j+1]);
        }
    }

    for (int i : arr) cout &lt;&lt; i &lt;&lt; endl;
}</code></pre>
<p>브론즈2인만큼 그냥 기본 버블 정렬하는 문제. 그런데 c++을 쓴지 진짜 오랜만이라 벡터나 다른거 쓰는걸 까먹어서 앞으로는 약간 주기적으로 풀면서 좀 익히고 정리해야할 것 같다. 버블 정렬 항상 하던건데 계속 <code>sort</code>를 써서 퀵소트로 자동으로 처리했어서 코드로 적는거 까먹어서 복기하는데 조금 고민했다. 대충 반복문 2개 돌려야하는 부분만 기억났고 인덱스 어떻게 해야하지? 순간 고민했는데 금방 떠올렸긴한데, 이게 맞나? 싶은 느낌. 그래서 제미나이한테 코드리뷰를 맡겨봤다.</p>
<p>일단 지적받은 부분은 아래와같다.</p>
<ol>
<li><p>가변길이 배열(VLA) 주의사항
코드에서 벡터를 안쓰고 그냥 배열을 썼는데, 대부분의 gcc 같은 컴파일러에서 허용해주긴하지만 c++ 표준이 아니니 <code>std;:vector&lt;int&gt; arr(n);</code>를 사용하라는 지적. 이게 메모리 관리 측면에서도 안전하다고 한다. 배열의 크기는 컴파일 타임에 결정되어야한다고.</p>
</li>
<li><p>정렬 로직 최적화
버블 정렬 자체가 한 번 끝까지 반복을 돌고 나면 맨 끝의 요소는 확정이 되는 구조인데, 위에 적은 j 인덱스는 이미 정렬이 끝난 마지막 요소까지 확인을 하고 있으니 <code>j&lt; n-1</code>를 <code>j&lt;n-1-i</code>로 범위를 줄이라는 지적이 있었는데 맞는말이라 수정하였다.
=&gt; 버블 정렬 정의에 적혀있는데 이걸 근자감으로 문제를 풀고 정독했다^^. 먼저 읽고 구현했으면 좀 더 신경써서 처리했을텐데..하하ㅏ</p>
</li>
<li><p>조기종료 조건 추가
이 부분은 생각치 못한 거였는데, <strong>정렬이 완료된 배열</strong>이 들어와도 위의 코드는 O(n^2)의 시간을 끝까지 다 쓰는데, 만약 한 번의 반복을 끝까지(0부터 n까지 1번 돌았을 때) 단 한 번도 swap이 일어나지 않았다면 이미 정렬된 상태이므로 그대로 바로 출력하도록 <code>break</code>을 걸어주는 편이 좋다고한다.</p>
</li>
<li><p><code>endl</code>보다 <code>\n</code>을 사용하는것
시간제한이 빡세지 않을 것 같은 문제라 그냥 <code>endl</code>을 쓰긴했는데, <code>endl</code>은 단순히 줄바꿈만 하지 않고 <strong>출력 버퍼를 비우는(flush) 작업</strong>까지 수행하기에 속도가 느리다고 한다. 알고리즘 문제 푸는 건 <code>\n</code>을 쓰는 습관을 들이라는 지적.</p>
</li>
</ol>
<ul>
<li>flush는 <strong>대화형(inetractive) 문제에서 필수적</strong>이라고 한다. 
예) 대화형 문제의 로직</li>
</ul>
<ol>
<li>내 프로그램이 질문 -&gt; 2. 이때 <strong>flush를 하지 않으면 상대(채점 시스템)은 내 질문을 읽지 못함</strong> -&gt; 3. 상대의 대답을 기다리기 위해 내 프로그램은 멈춰버림(deadlock)
=&gt; 아마 제미나이가 대화형 AI라 이런 예시를 들은거 같은데, 왠지 전에 자바 수업에서 소켓 통신 사용할 때, 문자열 보내는 것도 정말 꼬박꼬박 flush를 해주는 귀찮음을 줄이고자 자동 <code>flush</code>가 되도록 어떤 처리를 해주었던걸 생각하면 맞는 말인듯 하다.</li>
</ol>
<p>위의 요소를 전부 반영해 수정한 코드는 아래와 같이 나온다.</p>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;algorithm&gt;
using namespace std;

int main() {

    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int n;
    cin &gt;&gt; n;

    vector&lt;int&gt; v(n);
    for (int i=0; i&lt;n; i++) {
        cin &gt;&gt; v[i];
    }

    for (int i=0; i&lt;n; i++) {
        bool swapped = false;
        for (int j=0; j&lt;n-1-i; j++) {
            if (v[j] &gt; v[j+1]) {
                swap(v[j], v[j+1]);
                swapped = true;
            }    
        }
    }

    for (int i : v) cout &lt;&lt; i &lt;&lt; &quot;\n&quot;;
}</code></pre>
<hr>
<h2 id="합병-정렬-merge-sort">합병 정렬 (merge sort)</h2>
<ul>
<li><p>분할 정복(divide and conquer) 방식으로 설계된 알고리즘
<span style="color:gray"> =&gt; 처음 분할 정복이라는 단어를 들었을 때는 느끼지 못했는데 지금 생각해보면 컴퓨터 네트워크에서 DNS 쿼리 관련해서도 이런 거랑 비슷한 느낌을 받았다. 잘게 쪼개서 처리하는 부분이?</span></p>
</li>
<li><p>하나의 배열을 절반으로 나눔 -&gt; 나눈 배열들을 정렬 -&gt; 다시 하나의 배열로 합치기</p>
</li>
<li><p><strong>시간 복잡도: O(nlogn)</strong></p>
</li>
</ul>
<p>+<strong>분할 정복</strong></p>
<ul>
<li>한 번에 해결할 수 없는 문제를 작은 문제로 분할해 해결하는 알고리즘</li>
<li>주로 재귀함수로 구현</li>
<li>크게 3단계로 구성<ol>
<li>Divide: 문제 분할</li>
<li>Conquer: 쪼개진 작은 문제 해결</li>
<li>Combine: 해결된 작은 문제들을 다시 합침</li>
</ol>
</li>
</ul>
<h3 id="boj-2751-수-정렬하기2">BOJ 2751: 수 정렬하기2</h3>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;vector&gt;
using namespace std;

void merge(vector&lt;int&gt; &amp;v, int s, int m, int e) {
    int i =s;
    int j = m+1;

    vector&lt;int&gt; tmp;
    while (i &lt;= m &amp;&amp; j &lt;= e) {
        if (v[i] &lt;= v[j])tmp.push_back(v[i++]);
        else tmp.push_back(v[j++]);
    }

    if (i &gt; m) {
        while (j &lt;= e) tmp.push_back(v[j++]);
    }
    else {
        while (i &lt;= m) tmp.push_back(v[i++]);
    }

    for (int i : tmp) {
        v[s++] = i;
    }
}

void mergeSort(vector&lt;int&gt; &amp;v, int s, int e) {
    if (s &lt; e) {
        int mid =(s+e)/2;

        mergeSort(v,s, mid);
        mergeSort(v, mid+1, e);
        merge(v, s,mid,e);
    }
}

int main() {

    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int n;
    cin &gt;&gt; n;

    vector&lt;int&gt; v(n);
    for (int i=0; i&lt;n; i++) cin &gt;&gt; v[i];

    mergeSort(v, 0,n-1);
    for (int i : v) cout &lt;&lt; i &lt;&lt; &quot;\n&quot;;
}</code></pre>
<p>이전에 자료구조 수업을 들을 때 구현을 안했어서 합병 정렬을 처음 구현해보는건데, 이미지로는 쪼개서 합치고..하는게 그냥 그러려니 하는데 그걸 구현하는건 또 쉽지 않았다. 재귀함수를 써야하는건 알았는데 <strong>배열을 &quot;쪼개주는 부분&quot;과 &quot;합치는 부분&quot;을 함수를 나눠서 작성</strong>하는게(?) 조금 중요한 부분인 것 같다. 나눠서 작성해야하는 것까지는 뭔가 혼자 생각하다가 + 적당히 수업에서 들은걸로 기억했는데, 각 함수에서 어디까지 진행해야할 지 + 벡터를 반환해줘야할지-각 작은 단위 벡터를 반환해서 합치고, 합치고...지금 생각하면 이거 절대 안되겠지만?- 등 이래저래 많은 방법을 생각했다가 전부 실패했다.</p>
<p><code>mergeSort</code>에서 이제 배열을 쪼개는 역할을 하는데, <code>s</code>와 <code>e</code>의 중간인 <code>m</code>이라는 변수에서 계속 절반 부분의 인덱스를 저장해주면서 배열을 반씩 쪼개 재귀적으로 <code>mergeSort</code>를 호출하는게 중요한 부분인 것 같은 기분. </p>
<p>재귀함수에서는 무한루프를 돌지 않게 하기 위한 바닥 설정이 중요한 것 같은데 여기서는 <code>if (s&lt;e)</code>에서 이걸 체크해 만약에 <code>s==e</code>, 즉 배열의 크기가 1로 한 개의 요소만 남았을 때, 돌아가서 배열 크기가 1인 두 배열을 <code>merge</code> 해주면서 이제 천천히 합쳐지는? 느낌...이다.</p>
<p><code>merge</code>에서 이제 두 배열을 합쳐주는건데 사실 이걸 두 함수로 나누고 각 함수에서 할 역할을 정해 떠올리는게 어려웠지 합치는 부분의 구현은 간단했던 것 같다. </p>
<ol>
<li>인덱스를 사용해서 배열을 두 개로 사용할 수 있게</li>
<li>두 배열의 시작 부분 <code>i</code>와 <code>j</code>를 잘 설정해주기</li>
<li>두 배열을 왼쪽부터 비교해 작은거를 순서대로 임시배열(<code>tmp</code>)에 <code>push_back</code>을 통해 넣어주기</li>
<li><code>i</code> 또는 <code>j</code>가 끝에 도달했을 때 (<code>i &gt; m</code>이거나 <code>j &gt; e</code>일 때) 반복문 종료 후, 남은 배열 요소 전부를 <code>tmp</code>에 <code>push_back</code>해주기.</li>
<li><code>tmp</code> 배열에 저장한 값대로 실제 배열(벡터) 인덱스 <code>s</code>부터 <code>e</code> 까지 업데이트.</li>
</ol>
<p>마지막 5번이 조금 신기한 부분. 이 부분은 다른 사람들이 구현한 블로그글 참고해서 썼다. 도대체 어떻게 바꾸지??하다가 결국 역시 임시배열을 써주기는 해야했다. 음, 여기까지인데 역시 기본?이라고 해야하나 약간 잘해야하는데? 잘못해서 아쉬운. 기분이다. 이전에는 그냥 c++에 있는 <code>sort</code> 써서 문제를 풀어버렸는데 정말 이렇게 직접 구현하는건 쉽지 않은 것 같다.</p>
<p>이것도 제미나이?한테 코드리뷰를 또 맡겨봤다. 궁금하기도 하고 딱히 코드리뷰 받을데가 없다. 그리고 요즘 AI가 코딩 잘하니까ㅏ 다른 블로그 글들 직접 찾아보는 것도 좋은 방법이겠지만... 시간이슈?로.. 하하.</p>
<ol>
<li><p>잘했던 부분: <strong>안정 정렬</strong> 유지
이것도 사실 그냥 참고해서 구현하다보니 이대로 하게된건데 <code>if (v[i] &lt;= v[j])</code>의 부분에서 &quot;=&quot; 덕에 값이 같을 때 순서가 뒤바뀌지 않는 합병 정렬의 장점을 살렸다는데..음, 어차피 임시배열에 둘 중 뭘 저장할지 고르는거니까 사실 크게 상관없지 않나? 싶기도 하다.</p>
</li>
<li><p>매변 일어나는 메모리 할당
<code>merge</code> 함수 내에서 <code>vector&lt;int&gt; tmp</code>를 선언하는 것을 지적한다 -&gt; 그러면 도대체 어떻게 하라고? 싶었지만, 합병 정렬이 재귀호출이 많이 일어나는데, 그걸 합칠 때마다 새 벡터를 만들고 지우는건 좋지 않다고한다. -&gt; 그래서 어떻게 해결? 하하. <code>main()</code>에서 원본 배열과 같은 크기의 <code>tmp</code> 벡터를 하나 만들고 <code>merge()</code>에서 참조로 넘겨서 재사용하라고 한다. 근데 이게 진짜 좋은걸까 잘 모르겠다. 일단 하라는대로 해서 백준 돌려보면 메모리+시간이 나오니까 한 번 시도해볼까싶다.</p>
</li>
<li><p>정수 오버플로우 방지
<code>int mid = (s+e)/2</code>에서 아주 큰 배열에서는 <code>s+e</code>가 정수(<code>int</code>) 범위를 넘을 수도 있다는 위험을 지적한다. 그럴싸해서 해결책인 <code>int mid = s + (e-s)/2</code>로 바꾸면 좋겠다하는 생각이 들긴했다. 빼고 절반으로 나눈걸 더해서 가운데값을 찾으면 오버플로우 날 확률이 주니까...일단...?</p>
</li>
<li><p><code>push_back()</code>보다 인덱스 접근
앞에서 <code>tmp</code>를 메인에 만들라고 했는데 미리 크기를 지정하고 인덱스로 값을 넣는게 더 효율적이라고 한다...</p>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/56cc839f-1e64-4dcb-83fa-1baca6d64c23/image.png" alt=""></p>
<p>생각보다 꽤 줄어서 놀랐다. + 시간도 줄었다. 코드는 아래처럼 수정했다.</p>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;vector&gt;
using namespace std;

void merge(vector&lt;int&gt; &amp;v, vector&lt;int&gt; &amp;tmp, int s, int m, int e) {
    int i =s;
    int j = m+1;
    int k = s;

    while (i &lt;= m &amp;&amp; j &lt;= e) {
        if (v[i] &lt;= v[j]) tmp[k++] = v[i++];
        else tmp[k++] = v[j++];
    }
    while (j &lt;= e) tmp[k++] = v[j++];
    while (i &lt;= m) tmp[k++] = v[i++];

    for (int l=s; l&lt;= e; l++) {
        v[l] = tmp[l];
    }
}

void mergeSort(vector&lt;int&gt; &amp;v, vector&lt;int&gt; &amp;tmp, int s, int e) {
    if (s &lt; e) {
        int mid = s + (e-s)/2;

        mergeSort(v, tmp, s, mid);
        mergeSort(v, tmp, mid+1, e);
        merge(v, tmp, s,mid,e);
    }
}

int main() {

    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int n;
    cin &gt;&gt; n;

    vector&lt;int&gt; v(n);
    vector&lt;int&gt; tmp(n);
    for (int i=0; i&lt;n; i++) cin &gt;&gt; v[i];

    mergeSort(v,tmp, 0,n-1);
    for (int i : v) cout &lt;&lt; i &lt;&lt; &quot;\n&quot;;
}</code></pre>
<hr>
<h2 id="stdsort"><code>std::sort</code></h2>
<p>앞에서도 말했지만, c++에서는 사실 직접 구현하지 않아도 퀵정렬을 <code>sort()</code>를 사용해 처리할 수 있는걸로 알고 있다. 굳이 따지자면 지금까지는 이 <code>sort()</code>를 정말 단순히 정렬할 때만 썼는데, 인자를 잘 설정해주면 다양하게 써먹을 수 있다-고 한다.</p>
<ul>
<li>인자로 배열의 <strong>처음 시작 위치</strong>와 <strong>끝 위치</strong>를 전달</li>
<li>기본(아무것도 설정하지 않으면) <strong>오름차순</strong>으로 정렬해서 반환</li>
<li><strong>내림차순</strong>을 위해서는 세 번째 인자에 <strong>greater&lt;&gt;()</strong>을 넣어줘야함</li>
<li>세 번째 인자에 <strong>비교함수(cmp)</strong>를 넣으면 원하는 조건대로 정렬 가능</li>
<li>비교함수가 <strong>false</strong>를 반환할 경우 <strong>swap</strong>한다는 것을 유념해 비교함수 넣기.</li>
</ul>
<br>

<h3 id="boj-10825번-국영수">BOJ 10825번: 국영수</h3>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;algorithm&gt;
using namespace std;

struct student {
    string name;
    int kr;
    int math;
    int en;
};

bool comp(const student &amp;s1, const student &amp;s2) {
    if (s1.kr == s2.kr) {
        if (s1.en == s2.en) {
            if (s1.math == s2.math) {
                return s1.name &lt; s2.name;
            }
            return s1.math &gt; s2.math;
        }
        return s1.en &lt; s2.en;
    }
    return s1.kr &gt; s2.kr;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int n;
    cin &gt;&gt; n;

    vector&lt;student&gt; v(n);
    for (int i=0; i&lt;n; i++) {
        cin &gt;&gt; v[i].name;
        cin &gt;&gt; v[i].kr;
        cin &gt;&gt; v[i].en;
        cin &gt;&gt; v[i].math;
    }

    sort(v.begin(),v.end(),comp);

    for (student s : v) {
        cout &lt;&lt; s.name &lt;&lt; &quot;\n&quot;;
    }
}</code></pre>
<ol>
<li><p>구조체 정의하기
학생 구조체를 만들어서, 그 안에 이름, 국어, 영어, 수학의 성적을 담도록 정의.</p>
<pre><code class="language-cpp">struct student {
 string name;
 int kr;
 int en;
 int math;
}</code></pre>
</li>
<li><p><code>sort</code>를 사용할건데, 구조체의 안에있는 요소를 기준으로 할거라 비교함수를 정의해줘야한다.</p>
</li>
</ol>
<pre><code class="language-cpp">bool comp(const student &amp;s1, const student &amp;s2) {
    if (s1.kr == s2.kr) {
        if (s1.en == s2.en) {
            if (s1.math == s2.math) {
                return s1.name &lt; s2.name;
            }
            return s1.math &gt; s2.math;
        }
        return s1.en &lt; s2.en;
    }
    return s1.kr &gt; s2.kr;
}</code></pre>
<p>앞에서 정리했듯이 <code>false</code>를 반환되면 <code>swap</code>이 일어나는 것이니 그거 유의해서 규칙을 잘 정의하려고 했다.
<img src="https://velog.velcdn.com/images/summeryoung_/post/c1d9468b-428d-4de3-a3da-e54d2a8aab6d/image.png" alt=""></p>
<p>처음에는 if문(조건)으로 체크해서 false를 반환하는 식으로 코드를 짰는데 잘 안돌아가서, 비교문 자체를 return하는 식으로 변경했더니 잘 수정돼서 그대로 갔다.</p>
<hr>
<h2 id="셋set">셋(set)</h2>
<ul>
<li>키(key)라고 불리는 원소의 집합</li>
<li>키값을 정렬된 상태로 저장</li>
<li>키값을 중복없이 저장</li>
<li>검색, 삽입, 삭제에서의 시간 복잡도 O(logN)</li>
<li>랜덤한 인덱스의 데이터에 접근 불가</li>
</ul>
<h3 id="set의-반복자">set의 반복자</h3>
<table>
<thead>
<tr>
<th align="center">메소드</th>
<th align="center"></th>
</tr>
</thead>
<tbody><tr>
<td align="center"><code>s.begin()</code></td>
<td align="center">set의 마지막 부분에 대한 주소값 반환</td>
</tr>
<tr>
<td align="center"><code>s.end()</code></td>
<td align="center">set의 마지막 부분에 대한 주소값 반환</td>
</tr>
</tbody></table>
<h3 id="set의-용량">set의 용량</h3>
<table>
<thead>
<tr>
<th align="center">메소드</th>
<th align="center"></th>
</tr>
</thead>
<tbody><tr>
<td align="center"><code>s.size()</code></td>
<td align="center">s에 저장되어 있는 크기</td>
</tr>
<tr>
<td align="center"><code>s.max_size()</code></td>
<td align="center">s가 가질 수 있는 최대 크기</td>
</tr>
</tbody></table>
<h3 id="set의-삽입-삭제">set의 삽입, 삭제</h3>
<table>
<thead>
<tr>
<th align="center">메소드</th>
<th align="center"></th>
</tr>
</thead>
<tbody><tr>
<td align="center"><code>s.insert()</code></td>
<td align="center">s에 저장된 요소 삭제</td>
</tr>
<tr>
<td align="center"><code>s.erase()</code></td>
<td align="center">s에 저장된 요소 삭제</td>
</tr>
<tr>
<td align="center"><code>s.swap()</code></td>
<td align="center">s1과 s2를 서로 교환</td>
</tr>
<tr>
<td align="center"><code>s.clear()</code></td>
<td align="center">s의 요소들 전부 삭제</td>
</tr>
</tbody></table>
<h3 id="set의-기능">set의 기능</h3>
<table>
<thead>
<tr>
<th align="center">메소드</th>
<th align="center"></th>
</tr>
</thead>
<tbody><tr>
<td align="center"><code>s.find</code></td>
<td align="center">찾는 값이 있으면 해당 위치의 iterator 반환, 아니면 <code>s.end()</code> 반환</td>
</tr>
<tr>
<td align="center"><code>s.count()</code></td>
<td align="center">set에 저장된 요소들의 갯수 반환</td>
</tr>
</tbody></table>
<h3 id="c-셋의-구조-bstbinary-search-tree">C++ 셋의 구조: BST(Binary Search Tree)</h3>
<ul>
<li>하나의 parent(root)에 <strong>최대 2개</strong> child가 있음</li>
<li>부모의 왼쪽 서브 트리값들은 모두 부모 노드보다 작음</li>
<li>부모의 오른쪽 서브 트리값들은 모두 부모 노드보다 큼</li>
</ul>
<br>


<h3 id="boj-7785번-회사에-있는-사람">BOJ 7785번: 회사에 있는 사람</h3>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;set&gt;
#include &lt;vector&gt;
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int n;
    cin &gt;&gt; n;

    set&lt;string&gt; people;
    for (int i=0; i&lt;n; i++) {
        string name, state;
        cin &gt;&gt; name;
        cin &gt;&gt; state;

        if (state == &quot;enter&quot;) people.insert(name);
        else people.erase(name);
    }

    vector&lt;string&gt; v (people.begin(), people.end());

    for (int i=v.size()-1; i&gt;=0; i--) {
        cout &lt;&lt; v[i] &lt;&lt; &quot;\n&quot;;
    }
}</code></pre>
<p>셋을 배우고나서 하는거라 일단 셋을 사용했다. 셋이 자동으로 정렬해서 값을 저장해주니까, 사람들 이름을 굳이 다시 정렬하지 않아도 괜찮으니 셋을 사용하는 쪽으로 하라고 이렇게 알려준 것 같다. 대신 관건은 마지막 부분에서 정렬된 것의 역순으로 출력해야해서 어떻게 해야할까 고민이 되었다.</p>
<p>우선 제일 먼저 생각한건, 그냥 셋을 벡터로 만들고, 반복문을 사용해서 인덱스를 통해 역순으로 출력하는 것이었다. 그 코드는 위쪽이고...</p>
<p>셋 역순 출력하는 방법을 찾다가 반복자를 사용하는 방법이 있어서 해당 방법을 사용해봤다.</p>
<pre><code class="language-cpp">set&lt;string&gt;::reverse_iterator it(people.rbegin());
    for (; it != people.rend(); ++it) {
        cout &lt;&lt; *it &lt;&lt; &quot;\n&quot;;
    }</code></pre>
<ul>
<li><code>reverse_iterator</code>: 역방향 반복자. 일반적인 iterator가 첫 요소에서 끝요소로 이동하면, 이 반복자는 뒤에서부터 앞으로 이동.</li>
<li><code>people.rbegin()</code>: set의 마지막 요소를 가리키는 시작점.</li>
<li><code>people.rend()</code>: set의 첫번째 요소의 앞부분을 가리키는 끝점.</li>
<li><code>*</code>: 역참조 연산자. 반복자(iterator)인 <code>it</code>이 가리키고 있는 메모리 주소의 실제 저장하는 값을 꺼내라는 의미.</li>
</ul>
<p>위의 코드를 조금 수정해서 아래처럼 됐다.</p>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;set&gt;
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int n;
    cin &gt;&gt; n;

    set&lt;string&gt; s;
    for (int i=0; i&lt;n; i++) {
        string name, state;
        cin &gt;&gt; name;
        cin &gt;&gt; state;

        if (state == &quot;enter&quot;) s.insert(name);
        else s.erase(name);
    }

    set&lt;string&gt;::reverse_iterator iter;
    for (iter=s.rbegin(); iter != s.rend(); iter++) {
        cout &lt;&lt; *iter &lt;&lt; &quot;\n&quot;;
    }
}</code></pre>
<p>항상 반복문 통해서 인덱스로만 접근했어서 그런가 반복자를 사용하는 것 자체가 뭔가 낯설었는데 <code>reverse_iterator</code>가 아니라 <code>iterator</code>를 한 번 써보니까 좀 더 느낌이 잘 와닿았다.</p>
<hr>
<h2 id="맵-map">맵 (Map)</h2>
<ul>
<li>다양한 자료형의 데이터를 <strong>key-value 쌍</strong>으로 저장</li>
<li>키값을 정렬된 상태로 저장</li>
<li>키값을 중복 없이 저장</li>
<li>검색/삽입/삭제에서의 시간 복잡도는 <strong>O(logN)</strong></li>
<li>랜덤한 인덱스의 데이터에는 접근 불가</li>
<li>키값을 인덱스처럼 접근해서 key-value 삽입이 가능함</li>
</ul>
<h3 id="삽입삭제검색">삽입/삭제/검색</h3>
<p><code>insert</code>: <code>pair&lt;string,int&gt;(&quot;a&quot;,5);</code>와 같은 형태로 삽입 (중복삽입 시도 시 추가X)
<code>erase</code>: 키값을 통해 해당 원소 삭제
<code>find</code>: 키값을 통해 원소 검색</p>
<h3 id="boj-1620번-나는야-포켓몬-마스터-이다솜">BOJ 1620번: 나는야 포켓몬 마스터 이다솜</h3>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;map&gt;
#include &lt;string&gt;
#include &lt;cctype&gt;
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int n,m;
    cin &gt;&gt; n &gt;&gt; m;

    map&lt;int, string&gt; arr1;
    map&lt;string, int&gt; arr2;
    for (int i=0; i&lt;n; i++) {
        string s;
        cin &gt;&gt; s;

        arr1.insert(pair&lt;int,string&gt;(i,s));
        arr2.insert(pair&lt;string, int&gt;(s,i));
    }

    for (int i=0; i&lt;m; i++) {
        string q;
        cin &gt;&gt; q;

        if (isdigit(q[0])) {
            int tmp = stoi(q);
            tmp--;
            cout &lt;&lt; arr1.find(tmp)-&gt; second &lt;&lt; &quot;\n&quot;;
        }
        else cout &lt;&lt; arr2.find(q)-&gt;second+1 &lt;&lt; &quot;\n&quot;;
    }
}</code></pre>
<p>일단 포켓몬 이름-번호를 키-값의 쌍으로 저장하고 있어야하니까 맵으로 저장해야한다는 것은 알았다. 다만 이후에 들어오는 값이 문자인지 숫자인지 구분해야하는 것과 문자/숫자에 따라 대응되는 포켓몬 이름/번호를 찾아줘야한다는 것이 남았었다.</p>
<p>일단 문자/숫자 여부를 확인하는 것은 <code>isdigit()</code>을 사용해주었다. 처음에는 <code>string</code> 자체를 <code>isdigit()</code>의 인자로 넣어주었는데, 문자 하나만 넣어야한다. 왜냐하면 들어온 <strong>문자가 0~9 사이의 10진수 숫자인지</strong>에 대해 판별해주는 것이다. 그래서 첫 글자 하나만 (코드에는 <code>q[0]</code>으로 그냥 처리했다.) 판별해 이게 숫자면 <code>stoi()</code>를 통해 문자열을 정수로 바꿔 정수처럼 사용할 수 있게했다. 이렇게 하면 두 자릿수가 나오더라도 문제가 없으니.</p>
<p>관건은 이름이 나오면 포켓몬 번호를, 번호가 나오면 이름을 찾아줘야했던 건데. 맵은 키값을 기준으로 대응되는 값(value)를 찾아오기에 어떻게 해야할지 고민이 많았다. <code>map&lt;string, int&gt;</code> 이렇게 만들면 이름으로 번호는 찾을지언정, 번호로 이름을 찾기는 또 다른 문제니 말이다.</p>
<p>사실 처음에는 그냥 반복문 전체를 돌면서 찾게 했는데 당연히 시간초과가 났다. 다른 방법이 있긴하겠지만...? 직관적으로 떠올린것은 <code>map&lt;string, int&gt;</code>와 <code>map&lt;int, string&gt;</code> 이렇게 두 개의 맵을 만들어서 각각 이름/번호로 찾으면 되는 것이었다. (좀 무식한 방법인가? 싶기도한데...)</p>
<p>초기화할 때, 인덱스를 사용해 번호를 넣어주는 식으로 맵 초기화를 진행했고, 위에 쓴대로 키가 이름인 맵과 키가 번호인 맵 2가지를 만들어서 각각 <code>find()</code>를 통해 찾을 수 있게 하였다. 예상하지 못한 오류는 그냥 <code>find()</code>만해서 끝이 아니라 또 그 값을 이용할거라 <code>m.find(key)-&gt;second</code> 이렇게 적어주어야했다.</p>
<p>이것도 제미나이한테 코드리뷰를 맡겼다.</p>
<ol>
<li>맵에 값 저장할 때 <code>insert()</code> 대신 간단하게<pre><code class="language-cpp">arr1.insert(pair&lt;int, string&gt;(i,s));</code></pre>
위에 처럼 저장을 했는데-이걸 블로그 정리글에서 보면서 써서...</li>
</ol>
<pre><code class="language-cpp">arr1[i] = s;</code></pre>
<p>위의 방법대로 해서 굳이 <code>pair</code>를 만들어서 넣지 않아도 가능한 식이었다^^.....</p>
<ol start="2">
<li><p>맵의 요소 접근
위의 코드에서 나는 <code>find()</code>를 사용하고, 또 거기서 <code>-&gt;second</code> 이런식으로 접근을 했는데, 그 만약 맵에 반드시 데이터가 존재한다면 그냥 바로 <code>arr2[q]</code> 이렇게만 접근해도 값(value)를 얻을 수 있다고한다.</p>
</li>
<li><p>해시맵
그리고 이거는 검색 속도 최적화를 위해서 그냥 맵이 아니라 <code>#include&lt;unordered_map</code> 해서 해시맵을 쓰라고 한다. 시간 복잡도가 O(logN) -&gt; O(1)으로 줄어든다고...</p>
</li>
</ol>
<p>+) 해시맵 vs 맵
맵(map)은 <strong>자료가 정렬</strong>되어 보관되지만, 해시맵(HashMap)은 <strong>정렬하지 않은채</strong>로 자료를 보관함. 맵은 이진 탐색 트리로 접근하고, 해시맵은 배열로 접근한다고... 시간복잡도가 더 빠르긴 하지만, 탐색 성능이 안정적이지 못해, STL의 공식 컨테이너에 속하지 않고 따로 구현된 STL 라이브러리를 사용해야한다고 한다....</p>
<h3 id="boj-2002-추월">BOJ 2002: 추월</h3>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;map&gt;
#include &lt;vector&gt;
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int n;
    cin &gt;&gt; n;

    map&lt;string, int&gt; car;
    vector&lt;int&gt; index(n);

    for (int i=0; i&lt;n; i++) {
        string s;
        cin &gt;&gt; s;

        car[s] = i;
    }

    for (int i=0; i&lt;n; i++) {
        string s;
        cin &gt;&gt; s;

        index[i] = car[s];
    }

    int cnt = 0;
    for (int i=0; i&lt;n; i++) {
        for (int j=i+1; j&lt;n; j++) {
            if (index[i]&gt;index[j]) {
                cnt++;
                break;
            }
        }
    }

    cout &lt;&lt; cnt;

}</code></pre>
<p>아이디어 떠올리는게 제일 힘들었다. 일단 차량이름(?)하고, 들어온 순서를 키-값 쌍으로해서 저장해야하는것은 알겠는데, 이걸 후에 나가는 순서대로 이름이 들어왔을 때 어떻게 비교할지가 진짜 안떠올랐다. 여기저기서 그냥 힌트를 받았는데. <strong>차량이름이 아니라 차량이 들어왔던 순서</strong>로 나가는 차를 벡터/배열로 받는 것이다.</p>
<p>추월 했다는 말이 <strong>나의 뒤에 있는 인덱스가 나보다 작으면 = 나보다 먼저 들어갔던 차</strong>가 되니까, 맵에서 차가 들어온 순서를 값으로 가지고 있다가, 나가는 차의 입력에 대응되는 그 순서를 벡터에 저장해주고, 후에 벡터 순회하면서 만약 내 뒤에 있는 값들 중 나보다 순서가 작은게 있으면 나는 추월한 차라고 생각해서 <code>cnt++</code>을 통해 추월한 차의 수를 하나씩 업데이트해주었다.</p>
<p>시간제한이 2초라 벡터 순회는 그냥 for 문 이중으로 써서 확인하는 식으로 짰다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[밑시딥1] 챕터1 내용 정리]]></title>
            <link>https://velog.io/@summeryoung_/%EB%B0%91%EC%8B%9C%EB%94%A51-%EC%B1%95%ED%84%B01-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@summeryoung_/%EB%B0%91%EC%8B%9C%EB%94%A51-%EC%B1%95%ED%84%B01-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sun, 08 Mar 2026 13:54:36 GMT</pubDate>
            <description><![CDATA[<h1 id="챕터1-헬로-파이썬">챕터1. 헬로 파이썬</h1>
<ul>
<li>파이썬 기초 내용이랑 앞부분은 건너뛰고 넘파이&amp;맵플롯 위주로 실습 및 학습 진행</li>
<li>리스트/ 딕셔너리 관련 헷갈리는 부분 있어서 그 부분 정리</li>
</ul>
<h2 id="리스트">리스트</h2>
<pre><code class="language-python">a = [1,2,3,4] #리스트 생성
len(a) #리스트 길이 출력
a[0] #첫 원소에 접근
a[0:2] #인덱스 0부터 1까지 (2는 포함X)
a[1:] #인덱스 1부터 끝까지</code></pre>
<ul>
<li>C나 자바를 먼저 해서 배열과 다른 리스트를 사용하는 파이썬이 조금 헷갈리는 편…인데 인덱스 사용해서 접근하는건 똑같은데 슬라이싱이나 반복문 사용해서 값 저장하고 하는 부분이 달라 헷갈린다…</li>
</ul>
<h2 id="딕셔너리">딕셔너리</h2>
<pre><code class="language-python">
me = [&#39;height&#39;:180] #딕셔너리 생성
me[&#39;height&#39;] #원소에 접근
me[&#39;weight&#39;] = 70 #새 원소 추가</code></pre>
<ul>
<li>자바의 map이랑 비슷한 느낌. 키-값 쌍으로 저장함.</li>
<li>파이썬은 변수 타입을 선언하지 않아서 그 부분이 영 적응이 안됨… + C++이나 자바에서는 항상 생성하고 나서 썼는데 파이썬은 그러지 않아서 이 부분도 적응이 안됨..</li>
</ul>
<h2 id="클래스">클래스</h2>
<ul>
<li>클래스 자체는 자바에서 많이 사용해서 익숙한데 파이썬의 클래스는 매개변수 같은 부분이 좀 특이하다고 생각됐다.</li>
</ul>
<pre><code class="language-python">class 클래스명:
    def __init__(self, 인수, ...):
        ...
    def 메소드명(self, 인수, ...):
        ...</code></pre>
<ul>
<li><p><code>__init__</code>: 자바의 생성자. 특별한 메소드로 클래스를 초기화하는 역할로 클래스의 인스턴스가 만들어질 때 한 번만 불리는 메소드.</p>
</li>
<li><p><code>self</code>: 파이썬에서는 메소드의 첫 번째 인수로 자신(자신의 인스턴스)를 나타내는 self를 명시적으로 씀. ⇒ 이게 생소한 느낌…</p>
</li>
<li><p>실습코드</p>
</li>
</ul>
<pre><code class="language-python">class Man:
    def __init__(self, name):
        self.name = name
        print(&quot;Initialized&quot;)

    def hello(self):
        print(&quot;Hello &quot; + self.name)

    def goodbye(self):
        print(&quot;Bye &quot; + self.name)

m = Man(&quot;David&quot;)
m.hello()
m.goodbye()</code></pre>
<ul>
<li>Man이라는 클래스 정의 → Man 클래스에서 m이라는 인스턴스(객체) 생성.</li>
<li>Man의 생성자(초기화 메소드)는 name이라는 인수를 받고, 그 인수로 인스턴스 변수인 <code>self.name</code>을 초기화.</li>
<li>인스턴스 변수(=객체변수): 각 객체별로 저장되는 변수</li>
</ul>
<h2 id="⭐-넘파이numpy">⭐ 넘파이(numpy)</h2>
<ul>
<li>배열/행렬 계산을 편리하게 할 수 있도록 도와주는 라이브러리</li>
<li>넘파이의 배열 클래스 <code>numpy.array</code>에 편리한 메소드가 많음</li>
</ul>
<h3 id="넘파이-가져오기">넘파이 가져오기</h3>
<ul>
<li>넘파이는 외부 라이브러리이기에 임포트 필요</li>
</ul>
<pre><code class="language-python">import numpy as np</code></pre>
<ul>
<li>numpy 라는 외부 라이브러리를 np라는 이름/닉네임/별칭으로 가져오라는 의미.</li>
<li>이렇게 가져오면 앞으로 넘파이가 제공하는 메소드를 <code>np</code>를 통해 참조 가능</li>
</ul>
<h3 id="넘파이-배열-생성하기">넘파이 배열 생성하기</h3>
<ul>
<li><code>np.array()</code><ul>
<li>넘파이 배열 생성</li>
<li>파이썬의 리스트를 인수로 받아 넘파이 라이브러리가 제공해주는 특수한 형태의 배열(<code>numpy.ndarray</code>)를 반환.</li>
</ul>
</li>
</ul>
<pre><code class="language-python">x = np.array([1.0, 2.0, 3.0])

print(x)
&gt;&gt;&gt; [1,2,3]

type(x)
&gt;&gt;&gt; &lt;class &#39;numpy.ndarray&#39;&gt;</code></pre>
<h3 id="넘파이-산술연산">넘파이 산술연산</h3>
<ul>
<li>주의점: 배열 x와 배열 y의 원소 수가 같음! ⇒ 원소 수가 다르면 오류가 발생하기 때문에 주의<ul>
<li>x와 y의 원소 수가 같다면 산술 연산은 각 원소에 대해서 행해짐</li>
<li>원소별 = element-wise</li>
</ul>
</li>
<li>넘파이 배열: 원소별 계산 뿐만 아니라 넘파이 배열과 수치 하나(스칼라값)의 조합으로 된 산술연산도 수행 가능.<ul>
<li><strong>브로드캐스트</strong>: 스칼라값과의 계산이 넘파이 배열의 원소별로 한 번씩 수행 ⇒ 아마 B(a1+a2) = Ba1+Ba2 (B는 스칼라 벡터 a는 (a1, a2)인 것을 말하는거 같음.</li>
</ul>
</li>
</ul>
<pre><code class="language-python">x = np.array([1.0, 2.0, 3.0])
y = np.array([2.0, 4.0, 6.0])

x+y  #원소별 덧셈
&gt;&gt;&gt; array([3., 6., 9.])

x-y #원소별 뺄셈
&gt;&gt;&gt; array([-1., -2., -3.])

x*y #원소별 곱셈
&gt;&gt;&gt; array([2., 8., 18.])

x/y
&gt;&gt;&gt; array([0.5, 0.5, 0.5])</code></pre>
<h3 id="넘파이-n차원-배열">넘파이 N차원 배열</h3>
<ul>
<li>넘파이는 1차원 배열 뿐만 아니라 다차원 배열 역시 작성 가능</li>
<li>2차원 배열 작성하는 방법</li>
</ul>
<pre><code class="language-python">A = np.array([[1,2], [3,4]])
print(A)

#결과 [[1 2]
#            [3 4]]

A.shape
#결과 (2,2)

A.dtype
#결과 dtype(&#39;int64&#39;)</code></pre>
<ul>
<li><p>행렬의 형상은 <code>shape</code>로 알 수 있고, 행렬에 담긴 원소의 자료형은 <code>dtype</code>으로 알 수 있음.</p>
</li>
<li><p>서로 형상이 같은 행렬은 산술 연산도 대응하는 원소별로 계산됨</p>
<p>  (N차원의 배열에서 배열의 ‘각 차원의 크기(원소 수)’ = ‘배열의 형상’</p>
</li>
</ul>
<aside>

<p>✔️ NOTE</p>
<p>넘파이 배열(np.array)는 N차원 배열을 작성 가능. 수학에서 벡터와 행렬을 일반화한 것은 <strong>텐서(tensor)</strong>라고함. </p>
</aside>

<h3 id="브로드캐스트">브로드캐스트</h3>
<ul>
<li>넘파이에서 형상이 다른 배열끼리도 계산할 수 있음 ⇒ 2x2 행렬에 스칼라 10을 곱할 때, 스칼라 10이라는 값이 2x2의 행렬로 확장된 후 연산 이루어짐</li>
</ul>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/b95accfd-4e78-4726-a2e0-c0e702046fc3/image.png" alt=""></p>
<p>⇒ 일단은 그냥 스칼라 * 벡터(행렬? 배열?)의 곱 느낌으로 받아들임… 그거 표현한 것 같기도..?</p>
<pre><code class="language-python">A = np.array([[1,2], [3,4]])
B = np.array([10,20])

A*B
#결과 array([[10,40],
#                     [30,80]])</code></pre>
<ul>
<li>B가 1차원 배열임에도 2차원 배열인 A와 똑같은 형상으로 변형된 후에 원소별 연산이 이루어짐.</li>
</ul>
<aside>

<ul>
<li><p>❓ 브로드캐스트가 되면 사실 앞에서 x랑 y의 모양(shape)이 같아야한다고 했는데 아니지 않나?</p>
<ol>
<li><p>기본원칙: 모양이 같아야함</p>
<p>기본적으로 요소별로 산술연산이 이루어지기에 짝이 맞아야함</p>
</li>
<li><p>예외조항: 브로드캐스팅</p>
<p>넘파이는 연산하려는 두 배열의 모양이 달라도, 두 가지 조건 중 하나를 만족하면 큰 배열의 모양에 맞게 작은 배열의 모양을 확장시켜 연산을 수행함</p>
</li>
</ol>
<p>  <strong>[규칙1] 두 배열의 차원 수가 다르면, 차원이 낮은 배열의 앞(왼쪽)을 1로 채움</strong></p>
<p>  <strong>[규칙2] 각 차원에서 두 배열의 크기가 같거나, 어느 한쪽의 크기가 1이어야함</strong></p>
<p>  예) 
  케이스1: 2차원 &amp; 1차원의 연산
  배열A의 모양 (3,3) 3행 3열 | 배열B의 모양 (3,) 1행 3열처럼 보임</p>
<p>  ⇒ B의 모양을 규칙에 따라 (1,3)으로 바꿈. → A의 행(3)과 B의 행(1)을 비교해 1이 있기에 B를 3행으로 복사해 늘림 → 둘 다 (3,3)이 되어 연산 가능!</p>
<p>  케이스2: 연산 불가능한 경우
  배열A (3,2) | 배열B (3,)</p>
<ul>
<li><p>A의 마지막 차원은 <strong>2</strong>인데 B의 마지막 차원은 <strong>3</strong></p>
<p>⇒ 둘 다 1이 아니면서 서로 다르기에 넘파이가 늘리지 못해 에러 발생.</p>
</li>
</ul>
</li>
</ul>
</aside>

<h3 id="원소-접근">원소 접근</h3>
<ul>
<li>원소의 인덱스는 0부터 시작</li>
</ul>
<pre><code class="language-python">#원소 접근
X = np.array([[51,55], [14,19], [0,4]])
print(X)

print(f&quot;X[0]: {X[0]}&quot;) #0행
print(f&quot;X[0][1]: {X[0][1]}&quot;) #(0,1) 위치의 원소</code></pre>
<pre><code class="language-python">#원소 접근
X = np.array([[51,55], [14,19], [0,4]])
print(X)

print(f&quot;X[0]: {X[0]}&quot;) #0행
print(f&quot;X[0][1]: {X[0][1]}&quot;) #(0,1) 위치의 원소</code></pre>
<ul>
<li><code>flatten</code>: 평탄화 ⇒ 1차원 배열로 변환해줌</li>
</ul>
<pre><code class="language-python">#flatten: 평탄화
X = np.array([[51,55], [14,19], [0,4]])
X = X.flatten()

print(X)

#결과: [51 55 14 19  0  4]

#인덱스가 0,2,4인 원소 얻기 (앞에서 1차원 배열로 바꿨으니까)
X[np.array([0,2,4])]

#결과: array([51, 14,  0])</code></pre>
<ul>
<li>특정 조건을 만족하는 원소만 얻기</li>
</ul>
<pre><code class="language-python">#특정 조건만 만족하는 원소 얻기
print(X &gt; 15) #조건에 대한 각 원소의 T/F 나옴
print(X[X&gt;15])

#결과: [ True  True False  True False False]
#     [51 55 19]</code></pre>
<hr>
<h2 id="⭐-맷플롯립matplotlib">⭐ 맷플롯립(matplotlib)</h2>
<ul>
<li>딥러닝 실험에서는 그래프 그리기와 데이터 시각화 역시 중요함</li>
<li>맷플롯립: 그래프를 그려주는 라이브러리로 데이터 시각화가 쉬워짐.</li>
</ul>
<h3 id="단순한-그래프-그리기">단순한 그래프 그리기</h3>
<ul>
<li>그래프를 그리기 위해서는 <code>pyplot</code> 모듈 이용.</li>
</ul>
<pre><code class="language-python">import numpy as np
import matplotlib.pyplot as plt

#데이터 준비
x = np.arange(0,6, 0.1) #0에서 6까지 0.1 간격으로 생성
y = np.sin(x)

#그래프 그리기
plt.plot(x,y)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/b472b643-f58a-4bf4-98d5-b2238fc67117/image.png" alt=""></p>
<ul>
<li><code>arange</code> 메소드를 [0, 0.1, 0.2 … 5.9] 라는 데이터를 생성해 변수 x에 할당.</li>
<li>x의 각 원소에 넘파이의 sin 함수인 <code>np.sin()</code>을 적용해 변수 y에 할당.</li>
<li>x와 y를 인수로 <code>plt.plot</code> 메소드를 호출해 그래프를 그리고 <code>plt.show()</code>를 호출해 그래프를 화면에 출력!</li>
</ul>
<h3 id="pyplot의-기능">pyplot의 기능</h3>
<ul>
<li>cos 함수 그리기 + 제목과 각 축의 이름(레이블) 표시 등 pyplot의 다른 기능 사용하기</li>
</ul>
<pre><code class="language-python">from matplotlib.lines import lineStyles
#2. pylot의 기능

#데이터 준비
x = np.arange(0,6, 0.1)
y1 = np.sin(x)
y2 = np.cos(x)

#그래프 그리기
plt.plot(x, y1, label=&quot;sin&quot;)
plt.plot(x, y2, linestyle=&quot;--&quot;, label=&quot;cos&quot;) #cos함수는 점선으로 그리기
plt.xlabel(&quot;x&quot;) #x축 이름
plt.ylabel(&quot;y&quot;) #y축 이름
plt.title(&quot;sin&amp;cos&quot;) #제목

plt.legend()
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/ce249fac-471b-464b-873f-48f45f607bf3/image.png" alt=""></p>
<ul>
<li><code>plt.plot(x, y2, linestyle=&quot;--&quot;, label=&quot;cos&quot;)</code><ul>
<li><code>linestyle</code>: 점선 등 선의 종류 선택. 선의 모양 결정.</li>
<li><code>label</code>: 선의 이름표를 붙이는 역할. 나중에 범례(legend) 만들 때 해당 텍스트가 사용됨.</li>
</ul>
</li>
<li><code>plt.xlabel()</code>, <code>plt.ylabel()</code>:<ul>
<li>각 축이 무엇을 의미하는지 이름 붙여주기</li>
</ul>
</li>
<li><code>plt.legend()</code>:<ul>
<li><code>label</code>로 정해준 이름표들을 모아 그래프 한 구석에 범례(box)로 보여줌.</li>
</ul>
</li>
</ul>
<h3 id="이미지-표시하기">이미지 표시하기</h3>
<ul>
<li>pyplot에 있는 이미지 표시 메소드인 <code>imshow()</code></li>
<li><code>matplotlib.image</code>모듈의 <code>imread()</code>메소드를 사용해 이미지를 읽어옴.</li>
</ul>
<pre><code class="language-python">#3. 이미지 표시하기
from matplotlib.image import imread

img = imread(&#39;image.jpg&#39;)

plt.imshow(img)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/summeryoung_/post/c87b8dfd-71cd-489b-a5f5-5bc5ad6076b6/image.png" alt=""></p>
<hr>
<blockquote>
<p><strong>회고</strong>
원래 첫 주에 챕터2까지 하려고 했는데 개강 이슈?로 그냥 파이썬 기초 파트만 훑고 정리...깃허브에 대충 레포 파고 올리기만 했더니 지나가버렸다. ㅎㅎ. 되도록이면 챕터2 월요일 안으로 해서 올리기..를 일단 목표로하고... <br>
이전에 딥러닝 관련 동아리 하면서 matplotlib이나 numpy 잠깐 보기는 했는데 자세히 어떤걸 어떻게 사용하는지도 모르고 당시에 파이썬을 몰랐어서 이참에 다시 제대로 정리했다. 내용은 그렇게 많지 않았고..그냥 개강 이슈...로 아직 게으른 느낌.</p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>