<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>tori.log</title>
        <link>https://velog.io/</link>
        <description>인사이트를 얻고 정리하는 공간입니다</description>
        <lastBuildDate>Tue, 19 May 2026 23:59:37 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>tori.log</title>
            <url>https://velog.velcdn.com/images/jae-jang/profile/3de2cfeb-c689-4572-81df-36c6a0cbdda4/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. tori.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jae-jang" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[데이터 딕셔너리]]></title>
            <link>https://velog.io/@jae-jang/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%94%95%EC%85%94%EB%84%88%EB%A6%AC</link>
            <guid>https://velog.io/@jae-jang/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%94%95%EC%85%94%EB%84%88%EB%A6%AC</guid>
            <pubDate>Tue, 19 May 2026 23:59:37 GMT</pubDate>
            <description><![CDATA[<h3 id="개요">개요</h3>
<p>데이터 딕셔너리의 정보는 오라클의 테이블 스페이스에 저장된다</p>
<h3 id="1-파싱-정보-체크">1. 파싱 정보 체크</h3>
<pre><code class="language-sql">-- 하드 파싱 예제 생성
DECLARE
    v_cnt NUMBER;
BEGIN
    FOR i IN 1..10000 LOOP
        EXECUTE IMMEDIATE &#39;SELECT count(*) FROM emp WHERE empno =&#39; ||i
        INTO v_cnt;
    END LOOP;
END;
/

-- 위의 pl/sql로 만든 하드 파싱정보 체크
SELECT sql_text, sql_id, parse_calls, executions, plan_hash_value
FROM v$sql
WHERE sql_text LIKE &#39;SELECT count(*)%&#39;;</code></pre>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/6ae8e163-8439-483c-858d-62e441927f91/image.png" alt=""></p>
<h3 id="2-ndv-밀도-등의-정보">2. NDV, 밀도 등의 정보</h3>
<pre><code class="language-sql">
/*
옵티마이저 통계는 DBMS_STATS 패키지 또는 ANALYZE 명령문을 통해 수집 가능하며 수집된 통계 정보는
여러 DICTIONARY VIEW를 통해 내용을 확인할 수 있다.

아래의 EMP 테이블의 SAL 컬럼의 고유값(NDV)는 12 이고, DEPTNO 컬럼의 고유값은 3이다 
*/
SELECT COLUMN_NAME, NUM_DISTINCT, LOW_VALUE, HIGH_VALUE, DENSITY, NUM_NULLS
FROM USER_TAB_COLUMNS
WHERE TABLE_NAME = &#39;EMP&#39;
ORDER BY COLUMN_ID
;</code></pre>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/0e6f29b4-9705-430d-a5cb-d7f1f1cd4010/image.png" alt=""></p>
<h3 id="3-히스토그램-정보">3. 히스토그램 정보</h3>
<pre><code class="language-sql">SELECT column_name, num_distinct, density, num_buckets, histogram
 FROM user_tab_columns 
WHERE table_name = &#39;SALES&#39; ;

-- 히스토그램 통계 정보 생성: method_opt auto를 통해 버켓 개수 옵티마이저 자동 판단
BEGIN 
  DBMS_STATS.GATHER_TABLE_STATS(ownname       =&gt; USER
                               ,tabname       =&gt; &#39;CUSTOMERS&#39;
                               ,method_opt    =&gt; &#39;FOR COLUMNS CUST_CITY SIZE AUTO&#39; 
                               ,no_invalidate =&gt; FALSE ) ; 
END ; 
/</code></pre>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/a03d99c8-16e5-46de-bd6b-e16535d48ba9/image.png" alt=""></p>
<h3 id="4-인덱스-정보">4. 인덱스 정보</h3>
<pre><code class="language-sql">SELECT INDEX_NAME, COLUMN_NAME, COLUMN_POSITION
FROM USER_IND_COLUMNS
WHERE TABLE_NAME = &#39;EMPLOYEES&#39;;</code></pre>
<ul>
<li>아래와 같이 <code>EMPLOYEES</code>테이블의 인덱스 명, 적용된 컬럼명, 결합 인덱스 시 순서번호가 출력된다</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/295ef712-d5c4-4f74-bfc9-f7f189240a80/image.png" alt=""></p>
<h3 id="5-세션-권한">5. 세션 권한</h3>
<pre><code class="language-sql">/*
CONNECT
RESOURCE
SODA_APP
SELECT_TUNING_PRIVS: 
PLUSTRACE
*/
SELECT * FROM SESSION_ROLES;

-- ROLE에 포함된 객체 권한 확인
SELECT *
FROM ROLE_TAB_PRIVS
WHERE ROLE = &#39;SELECT_TUNING_PRIVS&#39;;</code></pre>
<ul>
<li><code>ROLE_TAB_PRIVS</code> 테이블로부터 <code>SELECT_TUNING_PRIVS</code> 권한의 세부 내용을 출력하면 아래와 같은 결과가 나온다
<img src="https://velog.velcdn.com/images/jae-jang/post/d1672aec-af95-4f7c-86f0-b46ab8ae7a22/image.png" alt=""></li>
</ul>
<h3 id="6-클러스터링-팩터">6. 클러스터링 팩터</h3>
<pre><code class="language-sql">-- 1. 인덱스 명 확인
SELECT INDEX_NAME, COLUMN_NAME, COLUMN_POSITION
FROM USER_IND_COLUMNS
WHERE TABLE_NAME = &#39;CUSTOMERS&#39;;

-- 2. 인덱스 명 기반으로 클러스터링 팩터 확인
-- CLUSTERING FACTOR = 51552.. row 수와 근접하므로 안좋다고 판단할 수 있음 
-- 즉, 인덱스보다 TABLE FULL SCAN 선택 (테이블 랜덤 액세스 많기 때문에)
-- 결과값: CUSTS_CITY_IX    1    161    620    **51552
SELECT index_name, blevel, leaf_blocks, distinct_keys, clustering_factor 
  FROM user_indexes 
 WHERE index_name = &#39;CUSTS_CITY_IX&#39; ;
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[SQL 시각 계산 팁]]></title>
            <link>https://velog.io/@jae-jang/SQL-%EC%8B%9C%EA%B0%81-%EA%B3%84%EC%82%B0-%ED%8C%81</link>
            <guid>https://velog.io/@jae-jang/SQL-%EC%8B%9C%EA%B0%81-%EA%B3%84%EC%82%B0-%ED%8C%81</guid>
            <pubDate>Tue, 19 May 2026 07:32:09 GMT</pubDate>
            <description><![CDATA[<h3 id="sql에서-n분-n초-전">SQL에서 n분, n초 전..</h3>
<pre><code class="language-sql">-- 현재 시각: 2026/05/19 16:29:35
SELECT SYSDATE FROM DUAL;

-- 1시간전: 2026/05/19 15:29:35
SELECT SYSDATE - (1/24) FROM DUAL;

-- 1분전: 2026/05/19 16:28:35
SELECT SYSDATE - (1/1440) FROM DUAL;

-- 1초전: 2026/05/19 16:29:14
SELECT SYSDATE - (1/86400) FROM DUAL;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[클러스터링 팩터]]></title>
            <link>https://velog.io/@jae-jang/%ED%81%B4%EB%9F%AC%EC%8A%A4%ED%84%B0%EB%A7%81-%ED%8C%A9%ED%84%B0</link>
            <guid>https://velog.io/@jae-jang/%ED%81%B4%EB%9F%AC%EC%8A%A4%ED%84%B0%EB%A7%81-%ED%8C%A9%ED%84%B0</guid>
            <pubDate>Tue, 19 May 2026 07:01:45 GMT</pubDate>
            <description><![CDATA[<h3 id="개요">개요</h3>
<p>본 장에서는 클러스터링 팩터가 좋은지 판단 근거와 개선하는 방법을 알아보자</p>
<p>클러스터링 팩터란 수직적 탐색 -&gt; 수평적 탐색을 통해서 찾은 인덱스의 rowid 들이 실제 테이블 블럭에 모여있는 정도를 뜻한다.</p>
<p><code>index range scan</code> 을 통해서 찾은 블록이 소량임에도 불구하고, 클러스터링 팩터가 좋지 않아 <code>table access by rowid</code> 블록수가 매우 큰 경우가 있다. 즉, 테이블 랜덤 액세스가 많아진 상황이라고 할 수 있는데, 이때 옵티마이저는 비용을 확인하여 인덱스 대신에 <code>table full scan</code> 을 사용할 수 도 있다.</p>
<h3 id="1-클러스터링-팩터-상태-파악">1. 클러스터링 팩터 상태 파악</h3>
<p>1) 올바른 히스토그램 정보 생성</p>
<ul>
<li>옵티마이저가 최적의 판단을 하기 위해서 </li>
</ul>
<pre><code class="language-sql">BEGIN 
  DBMS_STATS.GATHER_TABLE_STATS(ownname       =&gt; USER
                               ,tabname       =&gt; &#39;CUSTOMERS&#39;
                               ,method_opt    =&gt; &#39;FOR COLUMNS CUST_CITY SIZE AUTO&#39; 
                               ,no_invalidate =&gt; FALSE ) ; 
END ; 
/

SELECT /*+ FULL(C) */ *
FROM CUSTOMERS C
WHERE CUST_CITY = &#39;Los Angeles&#39;;
@XPLAN
/*
COST = 405
-----------------------------------------------------------------------------------------
| Id  | Operation         | Name      | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |           |      1 |        |    200 |00:00:00.01 |     359 |
|*  1 |  TABLE ACCESS FULL| CUSTOMERS |      1 |    940 |    200 |00:00:00.01 |     359 |
-----------------------------------------------------------------------------------------
*/

SELECT /*+ INDEX(C(CUST_CITY)) */ *
FROM CUSTOMERS C
WHERE CUST_CITY = &#39;Los Angeles&#39;;
@XPLAN

/*
버퍼의 개수는 상대적으로 FULL SCAN에 비해서 낮지만, 클러스터링 팩터가 안좋아서 COST 가 더 높음
- COST = 853
- 따라서 힌트 미사용시 FULL SCAN 을 선택한다
---------------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name          | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |               |      1 |        |    200 |00:00:00.01 |     159 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| CUSTOMERS     |      1 |    940 |    200 |00:00:00.01 |     159 |
|*  2 |   INDEX RANGE SCAN                  | CUSTS_CITY_IX |      1 |    940 |    200 |00:00:00.01 |       2 |
---------------------------------------------------------------------------------------------------------------
*/</code></pre>
<p>2) 힌트 미사용시 full table scan 을 선택</p>
<p>3) 데이터 딕셔너리에서 클러스터링 팩터를 확인해보자</p>
<pre><code class="language-sql">-- CLUSTERING FACTOR = 51552.. 너무 안좋다.. 그러니까 TABLE FULL SCAN 타지.. 딱봐도 테이블 랜덤 액세스 많음
SELECT index_name, blevel, leaf_blocks, distinct_keys, clustering_factor 
  FROM user_indexes 
 WHERE index_name = &#39;CUSTS_CITY_IX&#39; ;</code></pre>
<h3 id="2-클러스터링-팩터-개선">2. 클러스터링 팩터 개선</h3>
<p>클러스터링 팩터를 더 좋게 생성하기 위해서는 인덱스에 맞춰 정렬한 후 데이터를 생성하면된다.  <code>customers</code> 테이블을 기반으로 <code>cust2</code> 테이블을 생성하여 좋은 클러스터링 팩터를 만들어보자</p>
<p>1) 테스트용 테이블을 인덱스 컬럼에 맞춰 정렬해서 생성: 인위적으로 좋은 cf 를 위해..</p>
<pre><code class="language-sql">CREATE TABLE custs2
AS 
SELECT * FROM CUSTOMERS
ORDER BY CUST_CITY; -- FOR GOOD CLUSTERING FACTOR

CREATE INDEX CUST2_IX01 ON CUSTS2(CUST_CITY);

-- CLUSTERING_FACTOR = 1650 -&gt; 블록개수의 근접 = 좋은 클러스터링 팩터임
SELECT INDEX_NAME, BLEVEL, LEAF_BLOCKS, DISTINCT_KEYS, CLUSTERING_FACTOR
FROM USER_INDEXES
WHERE INDEX_NAME = &#39;CUST2_IX01&#39;;

-- COST = 405 비슷하네
SELECT /*+ full(c) */ * 
  FROM custs2 c
 WHERE cust_city = &#39;Los Angeles&#39;; 
 @XPLAN
 /*
 --------------------------------------------------------------------------------------
| Id  | Operation         | Name   | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |        |      1 |        |    200 |00:00:00.01 |     810 |
|*  1 |  TABLE ACCESS FULL| CUSTS2 |      1 |     90 |    200 |00:00:00.01 |     810 |
--------------------------------------------------------------------------------------
 */

-- COST = 4.. ?! -&gt; 클러스터링 팩터가 엄청 좋아짐
SELECT /*+ index(c CUST2_IX01) */ * 
  FROM custs2 c
 WHERE cust_city = &#39;Los Angeles&#39;; 
  @XPLAN
  /*
------------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |            |      1 |        |    200 |00:00:00.01 |       8 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| CUSTS2     |      1 |     90 |    200 |00:00:00.01 |       8 |
|*  2 |   INDEX RANGE SCAN                  | CUST2_IX01 |      1 |     90 |    200 |00:00:00.01 |       2 |
------------------------------------------------------------------------------------------------------------  
  */</code></pre>
<ul>
<li><code>CUST_CITY</code> 컬럼에 정렬하여 데이터를 insert 했더니 코스트가 무려 <code>853 -&gt; 4</code> 로 줄어든 모습을 확인할 수 있다</li>
</ul>
<p>2) 클러스터링 팩터 확인</p>
<pre><code class="language-sql">-- CLUSTERING_FACTOR = 1650 -&gt; 블록개수의 근접 = 좋은 클러스터링 팩터임
SELECT INDEX_NAME, BLEVEL, LEAF_BLOCKS, DISTINCT_KEYS, CLUSTERING_FACTOR
FROM USER_INDEXES
WHERE INDEX_NAME = &#39;CUST2_IX01&#39;;
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[실행계획 생성하는법 ]]></title>
            <link>https://velog.io/@jae-jang/%EC%8B%A4%ED%96%89%EA%B3%84%ED%9A%8D-%EC%83%9D%EC%84%B1%ED%95%98%EB%8A%94%EB%B2%95</link>
            <guid>https://velog.io/@jae-jang/%EC%8B%A4%ED%96%89%EA%B3%84%ED%9A%8D-%EC%83%9D%EC%84%B1%ED%95%98%EB%8A%94%EB%B2%95</guid>
            <pubDate>Mon, 18 May 2026 23:53:49 GMT</pubDate>
            <description><![CDATA[<h3 id="방법1-세션-정보-변경후-cursor-출력-추천">방법1: 세션 정보 변경후 cursor 출력 (추천)</h3>
<p>1) 파라미터 NULL 로 검색하기</p>
<pre><code class="language-sql">ALTER SESSION SET STATISTICS_LEVEL = &#39;ALL&#39;;

SELECT * FROM EMP; -- 2번 실행! -&gt; soft parsing 기준으로 튜닝 기준 판단을 위해

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, &#39;ALLSTATS LAST&#39;)); -- 인덱스 적용 안함</code></pre>
<p>2) sql id, child number 활용하기</p>
<pre><code class="language-sql">SELECT * 
FROM DEPT
WHERE DEPTNO = 10;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR);</code></pre>
<ul>
<li>아래와 같이 나오는 SQL_ID, child number  파라미터를 활용하여 실행계획을 검색할 수 있다
<img src="https://velog.velcdn.com/images/jae-jang/post/6164d2f9-6ccb-4b15-9819-c333810e8035/image.png" alt=""></li>
</ul>
<pre><code class="language-sql">SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(&#39;akvh5qj3gtkkp&#39;, &#39;0&#39;, &#39;ALLSTATS&#39;)); </code></pre>
<h3 id="방법2-힌트를-사용하기-비추">방법2: 힌트를 사용하기 (비추)</h3>
<pre><code class="language-sql">SELECT /*+ GATHER_PLAN_STATISTICS */ * FROM DEPT
WHERE DEPTNO = 10;
</code></pre>
<h3 id="방법3-오래-걸리는-쿼리인-경우">방법3: 오래 걸리는 쿼리인 경우</h3>
<pre><code class="language-sql">SELECT /*+ PGA_TEST */ * FROM T1 ORDER BY 1;
SELECT X.*
FROM V$SQL S
   , DBMS_XPLAN.DISPLAY_CURSOR(S.SQL_ID, S.CHILD_NUMBER, &#39;ALLSTATS LAST&#39;) X
WHERE S.SQL_TEXT LIKE &#39;SELECT /*+ PGA_TEST */%&#39;;

/*
----------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation          | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  | Writes |  OMem |  1Mem | Used-Mem |
----------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |      1 |        |    200 |00:00:31.93 |   15110 |  73251 |  80394 |       |       |          |
|   1 |  SORT ORDER BY     |      |      1 |    500K|    200 |00:00:31.93 |   15110 |  73251 |  80394 |    86M|  3193K|43008  (19|
|   2 |   TABLE ACCESS FULL| T1   |      1 |    500K|    500K|00:00:00.04 |    7916 |      0 |      0 |       |       |          |
----------------------------------------------------------------------------------------------------------------------------------
*/</code></pre>
<ul>
<li><p><code>/*+ PGA_TEST */</code> 와 같이 <code>sql</code> 식별을 위한 간단한 코멘트를 추가한 후에, <code>sql 문</code> 을 기반으로 실행계획을 탐색하는 방법이다</p>
</li>
<li><p>오래 걸리는 sql을 수행할 때, 사용하면 유용할 것 같다</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[옵티마이저 통계 정보]]></title>
            <link>https://velog.io/@jae-jang/%EC%98%B5%ED%8B%B0%EB%A7%88%EC%9D%B4%EC%A0%80-%ED%86%B5%EA%B3%84-%EC%A0%95%EB%B3%B4</link>
            <guid>https://velog.io/@jae-jang/%EC%98%B5%ED%8B%B0%EB%A7%88%EC%9D%B4%EC%A0%80-%ED%86%B5%EA%B3%84-%EC%A0%95%EB%B3%B4</guid>
            <pubDate>Mon, 18 May 2026 23:50:46 GMT</pubDate>
            <description><![CDATA[<h3 id="개요">개요</h3>
<p>본 절에서는 선택도. 밀도, 카디널리티 용어 개념과 히스토그램이 필요한 이유를 다룬다.</p>
<p>옵티마이저는 데이터 딕셔너리에 저장된 통계 정보를 기반으로 실행계획을 수립한다. 이때 꼭 알아둬야 하는 개념은 선택도와 밀도, 카디널리티 개념이다.</p>
<p>나아가 옵티마이저는 NDV에 대해서 균일한 로우수를 가지고 있다고 가정한다. 예로 들어서 EMP 테이블에 DEPTNO 컬럼의 NDV = 3 (부서가 유니크하게 3개만 있는 상황) , 총 로우수 = 30인 상황이면 옵티마이저는 각각의 부서마다 데이터가 10개씩 있다고 판단한다.</p>
<p>하지만 아래와 같은 데이터의 비대칭이 있는 경우가 있다.</p>
<blockquote>
<p>A 부서: 1명
B 부서: 9명
C 부서: 20명</p>
</blockquote>
<p>이럴 때는 히스토그램의 통계자료를 새롭게 생성해주는 것이 유리하다.</p>
<h3 id="1-용어-개념-선택도-밀도-카디널리티">1. 용어 개념: 선택도, 밀도, 카디널리티</h3>
<p>1) 선택도: 일반적으로 1 / NDV 로 계산하지만, predicate(조건식)이 늘어나면 값이 달라진다</p>
<p>아래와 같이 <code>deptno</code> 의 NDV 가 3이고, <code>gender</code> 의 NDV 가 2인 경우에는 <strong>선택도 = 1/3 * 1/2 로 계산</strong>한다</p>
<pre><code class="language-sql">select * from emp1
where deptno = 30 -- deptno NDV = 3
and gender = &#39;M&#39;; -- gender NDV = 2</code></pre>
<p>2) 밀도: 1 / NDV </p>
<p>3) 카디널리티: <strong>총로우수 * 선택도</strong> 로 계산된다</p>
<p>아래의 케이스에서는 <strong>30 * 1/3 = 10</strong> 이 카디널리티가 되겠다</p>
<pre><code class="language-sql">-- 총 로우수가 30 이라고 가정 
select * from emp1
where deptno = 30; -- deptno NDV = 3

4) NDV 보는법

```sql
/*
옵티마이저 통계는 DBMS_STATS 패키지 또는 ANALYZE 명령문을 통해 수집 가능하며 수집된 통계 정보는
여러 DICTIONARY VIEW를 통해 내용을 확인할 수 있다.

아래의 EMP 테이블의 SAL 컬럼의 고유값(NDV)는 12 이고, DEPTNO 컬럼의 고유값은 3이다 
*/
SELECT COLUMN_NAME, NUM_DISTINCT, LOW_VALUE, HIGH_VALUE, DENSITY, NUM_NULLS
FROM USER_TAB_COLUMNS
WHERE TABLE_NAME = &#39;EMP&#39;
ORDER BY COLUMN_ID
;</code></pre>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/72143137-c76e-42d7-9e30-8bc6a6de0edd/image.png" alt=""></p>
<pre><code>
### 2. 히스토그램

아래와 같이 비대칭이 심한 테이블이 있다.

```sql
SELECT channel_id, COUNT(*)
FROM SALES
GROUP BY ROLLUP(channel_id)
;

-- 결과 
channel_id COUNT(*)
2           258025
3           540328
4           118416
9           2074 * 비대칭이 심함
total      918843

-- 히스토그램 생성 x 시
BEGIN 
DBMS_STATS.GATHER_TABLE_STATS(ownname =&gt; USER
                              , tabname =&gt; &#39;SALES&#39;
                              , method_opt =&gt; &#39;for columns channel_id size 1&#39; -- BUCKET 1개 = 히스토리 X
                              , no_invalidate =&gt; FALSE);
END;
/
</code></pre><p><code>channel_id = 9</code> 일때 카디널리티가 2074임에도 불구하고, 히스토그램을 생성하지 않았을 때, 균등하게 분배한 <code>918843 * 1/4 = 229711</code> 개가 예상 실행 계획으로 잡히고 <code>full table scan</code> 을 선택했음을 알 수 있다.
<img src="https://velog.velcdn.com/images/jae-jang/post/e5a70da5-2b97-4cf1-9ab7-c72b31535a74/image.png" alt=""></p>
<p>히스토그램을 4개로 다시 생성해보자</p>
<pre><code class="language-sql">BEGIN
DBMS_STATS.GATHER_TABLE_STATS(ownname =&gt; USER
                            , tabname =&gt; &#39;SALES&#39;
                            , method_opt =&gt; &#39;for columns channel_id size 4&#39;
                            , no_invalidate =&gt; FALSE);
END;
/</code></pre>
<p>카디널리티가 정상적으로 잡히고 옵티마이저가 <code>index range scan</code> 을 선택한 것을 볼 수 있다!</p>
<p><img src="blob:https://velog.io/10a6ad27-85c6-4f5b-96cd-2ff1bd637e21" alt="업로드중.."></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[엑셀 설정]]></title>
            <link>https://velog.io/@jae-jang/%EC%97%91%EC%85%80-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@jae-jang/%EC%97%91%EC%85%80-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Sun, 08 Feb 2026 08:09:13 GMT</pubDate>
            <description><![CDATA[<h3 id="1-하이퍼링크-제거하기">1. 하이퍼링크 제거하기</h3>
<blockquote>
<p>파일 &gt; 옵션 &gt; 언어교정 &gt; 자동고침 옵션 &gt; 입력할 때 자동 서식 탭</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/6b54605a-2bdc-48f8-af14-c22d51e12233/image.png" alt=""></p>
<ul>
<li>해당 부분 해제시 이메일 주소 같은거 입력할 때 하이퍼 링크가 적용되지 않는다.</li>
</ul>
<h3 id="2-한영-자동-변환--영어-첫글자-대문자-제거하기">2. 한영 자동 변환 &amp; 영어 첫글자 대문자 제거하기</h3>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/95032b63-f9a6-42cd-a551-3b44e69d5938/image.png" alt=""></p>
<ul>
<li>사진상의 부분을 비활성화 해주자 </li>
</ul>
<h3 id="3-명령어-추가하기-오름차순-내림차순-병합">3. 명령어 추가하기 (오름차순, 내림차순, 병합)</h3>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/85375425-5e86-49f8-ac37-9a06e87ec1c2/image.png" alt=""></p>
<ul>
<li>빠른 실행 도구를 추가해주자 </li>
</ul>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/0cb98cac-dab4-44b4-842d-e0c826fa83e0/image.png" alt=""></p>
<ul>
<li>빠른 실행 도구 모음에서 많이 사용하는 명령어를 다음과 같이 추가해주자</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/0b9fcb3b-f6ab-4b6d-87e3-d1d9b8936f4f/image.png" alt=""></p>
<ul>
<li>내가 추가해준 명령어는 <code>alt</code> 를 클릭해서 쉽게 활용할 수 있다. 예로 들어서 1번이 오름차순인 경우, 인구수에 하나의 셀에서 <code>alt + 1</code> 만으로 바로 정렬이 되는 것이다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Enum 사용법 | 최상위문 | Null 병합 연산]]></title>
            <link>https://velog.io/@jae-jang/Enum-%EC%82%AC%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@jae-jang/Enum-%EC%82%AC%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Sat, 27 Dec 2025 05:47:57 GMT</pubDate>
            <description><![CDATA[<h3 id="enum-사용법">Enum 사용법</h3>
<pre><code class="language-c">class Program
{
    static void Main(String[] args)
    {
        Days restDay = Days.Monday;

        if (restDay == Days.Sunday)
        {
            Console.WriteLine(&quot;일요일 입니당&quot;);
        }
        else
        {
            Console.WriteLine(&quot;일요일 아님&quot;);
        }
        Console.ReadKey();
    }
}

enum Days
{
    Sunday,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday
}</code></pre>
<hr>
<h3 id="최상위문">최상위문</h3>
<p><code>C#</code> 에서 최상위문이란 <code>Main</code> 을 생략해도 자동으로 만들어준다는 뜻이다.</p>
<p><code>Main</code> 문은 아래 예제와 같이 <code>Class</code> 내부에 위치한다.</p>
<pre><code class="language-c">class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(args.Length);
    }
}</code></pre>
<p>그런데 아래의 예제 코드가 위의 코드와 완벽하게 동일하다.</p>
<pre><code class="language-c">Console.WriteLine(args.Length);</code></pre>
<p>그 이유는 컴파일러가 자동으로 <code>Main</code> 문을 만들어주기 때문이다. 이러한 <code>C#</code> 의 문법을 최상위문이라고 부르며, 실무에서는 사용하지는 않을 것 같다. 단순히 테스트 용도 정도로는 유용하게 사용되지 않을까?</p>
<hr>
<h3 id="null-병합-연산">Null 병합 연산</h3>
<p><code>C#</code> 에서는 <code>int a = null</code> 코드가 에러가 난다. 왜냐하면 <code>int</code> 값에 <code>null</code> 을 허용하지 않기 때문이다. 그렇기 때문에 <code>null</code> 값을 <code>int</code> 에 받고 싶다면 아래와 같이 코드를 작성한다.</p>
<pre><code class="language-c">//모두 같은 의미임
Nullable&lt;int&gt; a = null;
int? a = null; </code></pre>
<p>개발을 할 때 의미가 중요하기 때문에 <code>Nullable&lt;int&gt; a = null</code> 방식으로 작성하는 것이 더욱 명시적이라서 해당 방식을 많이 활용할 것 같다.</p>
<p>해당 <code>Nullable</code> 객체는 아래와 같이 정의되어 있다. 아직 모든 메서드를 잘 알지는 못하지만 적어도 <code>HasValue</code> 메서드는 자주 사용할 것 같다.</p>
<pre><code class="language-c">public Nullable(T value)
{
    public readonly bool HasValue { get; } 
    public readonly T HasValue { get; }
    public override bool Equals(object? other);
    public override int GetHashCode();
    public readonly T GetValueOrDefault();
    ...
}</code></pre>
<p>마지막으로 <code>Nullable</code> 객체를 활용하여 <code>Null 병합 연산</code> 은 다음과 같다.</p>
<pre><code class="language-c">int? a = null; 
int? b = null;

// b가 null이면 a에 3을 대입해라. b가 null이 아니면 b값을 a에 대입
a = b ?? 3; 

Console.WriteLine(&quot;a: &quot; + a); // 3
Console.WriteLine(&quot;b: &quot; + b); // null</code></pre>
<hr>
<pre><code class="language-c">int? a = null; 
int? b = 3;

a ??= b;  //a가 null이면 b 값을 할당해라

Console.WriteLine(&quot;a: &quot; + a); //3
Console.WriteLine(&quot;b: &quot; + b); //3
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[다형성2: 추상 클래스 VS 인터페이스]]></title>
            <link>https://velog.io/@jae-jang/%EB%8B%A4%ED%98%95%EC%84%B12-%EC%B6%94%EC%83%81-%ED%81%B4%EB%9E%98%EC%8A%A4-VS-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</link>
            <guid>https://velog.io/@jae-jang/%EB%8B%A4%ED%98%95%EC%84%B12-%EC%B6%94%EC%83%81-%ED%81%B4%EB%9E%98%EC%8A%A4-VS-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</guid>
            <pubDate>Sun, 28 Sep 2025 12:35:10 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>좋은 객체 지향 설계 원칙중 하나인 OCP 원칙은 다형성인 인터페이스, 추상 클래스 등을 이용하여 준수할 수 있다. 이때 구현 VS 상속을 확실히 구분하여 언제 무엇을 사용하는 것이 좋을지에 대해서 감을 잡아보자.</p>
<blockquote>
<p>OCP 는 코드 기능의 확장에는 열려 있고, 코드 수정시 최대한 기존의 코드의 변경을 줄이는 것이다.</p>
</blockquote>
<h2 id="추상-클래스">추상 클래스</h2>
<p>추상 클래스는 왜 만들어졌을까? 본격적인 논의 전에 아래의 그림을 살펴보자.
<img src="https://velog.velcdn.com/images/jae-jang/post/f1610e44-633e-4512-96dc-bcfe83cddbd7/image.png" alt=""></p>
<ul>
<li>전형적인 상속 구조이다. <code>Animal</code> 의 <code>sound</code> 메서드를 3개의 자식 클래스가 상속받고 있다. 그런데 <code>Animal</code> 클래스가 생성하여 활용하는 것이 의미가 있을까?<pre><code class="language-java">Animal animal = new Animal(); //이거 쓸 수 있어??</code></pre>
</li>
<li>의미 없다. 따라서 이러한 <code>Animal</code> 은 <strong>생성 용도가 아닌, 상속 용도로만 활용</strong>하는 클래스 라는 것을 알려주기 위해서 추상 클래스 개념이 도입된 것이다.</li>
</ul>
<pre><code class="language-java">public abstract class Animal{
    public void sound() {print(&quot;동물이 웁니다.&quot;)} //상속 기능 제공
    public abstract void move(); //모든 자식 클래스가 의무적으로 오버라이딩 해야함 
}</code></pre>
<h2 id="인터페이스">인터페이스</h2>
<pre><code class="language-java">//순수 추상 클래스
public abstract class AbstractAnimal {
   public abstract void sound();
   public abstract void move();
}

//인터페이스 
public interface InterfaceAnimal {
   void sound();
   void move();
}</code></pre>
<ul>
<li>위의 두 클래스는 동일한 역할을 수행한다. 인터페이스에서는 <code>public abstract</code> 키워드를 생략할 수 있어서 간편하다.</li>
</ul>
<h3 id="인터페이스는-다중-구현이-가능하다">인터페이스는 다중 구현이 가능하다.</h3>
<pre><code class="language-java">public interface InterfaceA {
    void methodA();
    void methodCommon(); //공통 메서드임
}
public interface InterfaceB {
    void methodB();
    void methodCommon(); //공통 메서드임
}
public class Child implements InterfaceA, InterfaceB{
    @Override
    public void methodA(){print(&quot;Child.methodA&quot;)}
    @Override
    public void methodA(){print(&quot;Child.methodB&quot;)}
    @Override
    public void methodCommmon(){print(&quot;child.methodCommon&quot;)} //하나만 구현
}
Interface a = new Child();
a.methodCommon();
//a.methodB(); //요고는 안됨 InterfaceB를 통해서 접근</code></pre>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/090a89be-5873-41b0-a98f-a0742e4e3bda/image.png" alt=""></p>
<ul>
<li>인터페이스에서 다중구현이 허용되는 이유는 공통되는 메서드는 한번만 구현하면 되기 때문이다. 상속의 다중구현인 경우 공통된 메서드를 자식 클래스 입장에서 어떻게 받아들어야 하는지 혼란이 와서 안되지만, 반면에 인터페이스는 가능하다.</li>
</ul>
<h2 id="상속-vs-구현">상속 vs 구현</h2>
<p>부모 클래스의 기능을 자식이 물려받을 때는 상속, 인터페이스는 구현한다는 표현을 한다. 이들을 구분하는 기준은 <strong>부모의 기능을 물려주는지에 대한 여부</strong>이다. 추상클래스는 자식 클래스에게 본인의 기능을 물려줄 수 있다. 반면에 인터페이스는 구현해야 되는 메서드를 강제한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[다형성의 이해1: 다형적 참조, 오버라이딩]]></title>
            <link>https://velog.io/@jae-jang/%EB%8B%A4%ED%98%95%EC%84%B1%EC%9D%98-%EC%9D%B4%ED%95%B41-%EB%8B%A4%ED%98%95%EC%A0%81-%EC%B0%B8%EC%A1%B0-%EC%98%A4%EB%B2%84%EB%9D%BC%EC%9D%B4%EB%94%A9</link>
            <guid>https://velog.io/@jae-jang/%EB%8B%A4%ED%98%95%EC%84%B1%EC%9D%98-%EC%9D%B4%ED%95%B41-%EB%8B%A4%ED%98%95%EC%A0%81-%EC%B0%B8%EC%A1%B0-%EC%98%A4%EB%B2%84%EB%9D%BC%EC%9D%B4%EB%94%A9</guid>
            <pubDate>Sun, 28 Sep 2025 12:09:05 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>객체 지향 프로그래밍에서 제공하는 <strong>다형성</strong> 에 대해서 살펴보자. 다형성은 공통 부분(메서드, 변수)을 자식 클래스에게 상속해주거나, 역할과 구현의 기능을 분리하는 강력한 기능을 제공한다. 이를 이해하기 위해서는 <strong>다형적 참조와 메서드 오버라이딩</strong>을 이해해야 한다.</p>
<h2 id="1-다형적-참조">1. 다형적 참조</h2>
<p>다형적 참조란 자바에서 부모 타입은 자신은 물론, 자신을 기준으로 모든 자식 타입을 참조할 수 있다는 뜻이다.</p>
<pre><code class="language-java">//다형적 참조 예시
Parent poly = new Child(); //이게 다형적 참조임
//Child  child   = new Parent(); //이건 안됨

public Class Parent{
    public void parentMethod(){
        print(&quot;parentMethod&quot;)
    }
}

public Class Child extends Parent{
    public void childMethod(){
        print(&quot;childMethod&quot;)
    }
}</code></pre>
<p>이때 <code>Parent poly = new Child()</code> 를 유심히 살펴보자. 이러한 <code>poly</code> 가 생성되면 아래와 같이 인스턴스가 만들어진다.
<img src="https://velog.velcdn.com/images/jae-jang/post/a85dc3f1-f14c-40f7-81ff-4496dc0f802e/image.png" alt=""></p>
<ul>
<li><p>인스턴스 내부에 <code>Parent</code> <code>Child</code> 모두 생성되지만 <code>poly</code> 는 부모 타입을 가르키기 때문에 <code>childMethod</code> 의 접근이 힘들다.</p>
</li>
<li><p>이러한 이유로 다형적 참조를 활용할 때 캐스팅이 언급된다. <code>poly</code> 를 다운 캐스팅하면 <code>childMethod</code> 접근이 가능하기 때문이다.</p>
</li>
</ul>
<pre><code class="language-java">Child child = (Child) poly;
child.childMethod();</code></pre>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/e1482cef-b699-4b38-ade6-0b1f8abdd4cd/image.png" alt=""></p>
<h3 id="캐스팅시-주의점">캐스팅시 주의점</h3>
<p>캐스팅인 업, 다운 2가지 종류가 있다. 자바에서는 업캐스팅은 <code>Parent poly = new Child()</code> 처럼 다형적 참조를 활용할 수 있기에 오히려 권장된다.
다만 다운 캐스팅은 런타임 에러가 발생할 수 있기 때문에 명시적으로 <code>(Child) poly</code> 처럼 작성해야 한다. 참고로 <code>instanceof</code> 키워드를 활용하여 캐스팅 가능 여부를 체크하는 것이 매우 중요하다. 아래의 코드를 살펴보자</p>
<pre><code class="language-java">Parent parent = new Parent();
Child child = (Child) parent;
child.childMethod(); //런타임 에러 발생</code></pre>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/c0bbe58d-48cc-42b6-94a9-8aeeb686f3eb/image.png" alt=""></p>
<ul>
<li><code>parent</code> 의 인스턴스 내부에는 <code>child</code> 인스턴스를 포함하지 않아서 런타임 에러가 발생한다. 이러한 이유로 <code>instanceof</code> 함수를 활용하여 참조하는 인스턴스 타입을 확인해야 한다.<pre><code class="language-java">Parent parent = new Parent();
// parent 가 Child 인스턴스를 참조하는 경우
if (parent instance of Child){
  Child child = (Child) parent; //다운 케스팅
  child.childMethod();
}</code></pre>
</li>
</ul>
<h2 id="2-메서드-오버라이딩">2. 메서드 오버라이딩</h2>
<p>앞서 상속파트에서 일부 오버라이딩 메모리 구조를 파악했지만, 이번 장에서는 다형적 참조 시 메서드 오버라이딩의 동작 방식을 집중적으로 살펴보자.</p>
<pre><code class="language-java">public class Parent {
    String value = &quot;parent&quot;
    public void method() {
        print(&quot;parentMethod&quot;)
    }
}

public class Child extends Parent {
    String value = &quot;child&quot;

    @Override
    public void method() {
        print(&quot;childMethod&quot;)
    }    
}

Parent poly = new Child();
poly.method(); //부모와 자식 메서드 중 뭐가 실행될까?</code></pre>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/75d4527f-51dc-49af-ac71-7e2e5d452209/image.png" alt=""></p>
<ul>
<li><code>poly</code> 는 먼저 <code>Parent method</code> 에 접근하지만, 자식 클래스에 오버라이딩한 <code>method</code> 가 있으면 그 메서드가 우선순위를 가진다. 따라서 위 코드에서는 <code>childMethod</code> 가 프린티 된다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바 상속 시 메모리 구조]]></title>
            <link>https://velog.io/@jae-jang/%EC%9E%90%EB%B0%94-%EC%83%81%EC%86%8D-%EC%8B%9C-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@jae-jang/%EC%9E%90%EB%B0%94-%EC%83%81%EC%86%8D-%EC%8B%9C-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Sat, 27 Sep 2025 06:01:16 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>자바에서 제공하는 상속의 기능은 공통부분을 부모에서 제공함으로써 유지보수의 용이함에 도움을 준다. 이러한 상속을 이해하기 위해서 힙 영역에서 <strong>인스턴스가 어떻게 생성되는지</strong> 이해할 필요가 있다.</p>
<h2 id="상속-시-메모리-구조">상속 시 메모리 구조</h2>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/5f29590e-a206-4873-9c0f-8af896b766be/image.png" alt=""></p>
<ul>
<li><p>그림과 같이 <code>Car</code> 라는 부모 객체를 2개의 자식이 상속받고 있다. 이 덕분에 전기차와 가스차에서 모두 <code>move</code> 의 기능을 공통적으로 활용할 수 있게 된다.</p>
</li>
<li><p>이때 <code>ElecticCar</code>를 생성할 때 메모리 구조는 어떻게 될까?</p>
<pre><code class="language-java">public class Car{
  public void move(){
      print(&quot;차가 움직입니다.&quot;)
  }
}
</code></pre>
</li>
</ul>
<p>public class EletricCar extends Car{
    public void charge(){
        print(&quot;충전합니다.&quot;)
    }
}</p>
<p>EletricCar electricCar = new EletricCar();</p>
<pre><code>![](https://velog.velcdn.com/images/jae-jang/post/9021c88d-9c16-4517-a214-8f5d42b1d9ab/image.png)

- 정답은 **부모의 인스턴스와 자식의 인스턴스가 동시에 생성**이 된다. 

- 이러한 이유로 부모 클래스를 상속받을 때, 자식 클래스의 생성자에서 부모 클래스의 생성자인 `super` 를 꼭 기입해줘야 한다. (없으면 부모의 기본 생성자를 JVM이 자동으로 실행해줘서 오류가 발생하지 않은 것이다)

## 자식 클래스에서 부모 메서드 호출하기

![](https://velog.velcdn.com/images/jae-jang/post/712783f0-95ec-48b6-9b80-7a1e7dc79192/image.png)

- `electricCar.move` 호출 시

- 본인 타입 (전기차) 를 기준으로 `move` 를 찾고, 만약에 없다면 부모 클래스에서 찾는다. 부모 클래스에도 없다면 상위 부모 클래스로 접근하여 계속 `move` 클래스를 찾는다.



</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[파이썬 조합, 순열]]></title>
            <link>https://velog.io/@jae-jang/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%A1%B0%ED%95%A9-%EC%88%9C%EC%97%B4</link>
            <guid>https://velog.io/@jae-jang/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%A1%B0%ED%95%A9-%EC%88%9C%EC%97%B4</guid>
            <pubDate>Tue, 16 Sep 2025 02:15:53 GMT</pubDate>
            <description><![CDATA[<h2 id="중복-순열">중복 순열</h2>
<p>기호로 <code>n_ㅠ_r</code> 이고 연산 결과는 <code>n ** r</code> 이다. 이는 n개의 서로 다른 원소들을 중복을 허락하여 r개 뽑은 후 배치하는 것이다.</p>
<blockquote>
<pre><code class="language-python">from itertools import product
for a in product([1, 2, 3], repeat=2):
    print(a)</code></pre>
</blockquote>
<p>```</p>
<ul>
<li>연산 결과는 (1, 1), (1, 2), (1, 3) ... (3, 3) 으로 총 3 ** 2 인 9가지가 나온다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[SQL WITH]]></title>
            <link>https://velog.io/@jae-jang/SQL-WITH</link>
            <guid>https://velog.io/@jae-jang/SQL-WITH</guid>
            <pubDate>Wed, 10 Sep 2025 11:46:11 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><code>with</code> 문을 사용하면 테이블을 <strong>커스터 마이징</strong>하여 사용할 수 있다</p>
</blockquote>
<h3 id="예제">예제</h3>
<pre><code class="language-sql">WITH counter AS (
    SELECT h.hacker_id, h.name, count(*) AS challenges_created
    FROM Hackers h
        INNER JOIN Challenges C ON h.hacker_id = C.hacker_id
    GROUP BY h.hacker_id, h.name
)
SELECT hacker_id, name, challenges_created
FROM counter</code></pre>
<ul>
<li><p><code>with counter</code> 를 통해 <code>counter</code> 라는 임시 테이블을 만들어주었다.</p>
</li>
<li><p>이를 활용하여 반복되는 서브쿼리를 획기적으로 줄일 수 있다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[파이썬 컬렉션]]></title>
            <link>https://velog.io/@jae-jang/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%BB%AC%EB%A0%89%EC%85%98</link>
            <guid>https://velog.io/@jae-jang/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%BB%AC%EB%A0%89%EC%85%98</guid>
            <pubDate>Wed, 06 Aug 2025 07:14:55 GMT</pubDate>
            <description><![CDATA[<h3 id="counter">Counter</h3>
<pre><code class="language-python">from collections import Counter

ex_list = [&#39;kim&#39;, &#39;kim&#39;, &#39;park&#39;, &#39;choi&#39;, &#39;kim&#39;, &#39;kim&#39;, &#39;kim&#39;, &#39;choi&#39;, &#39;park&#39;, &#39;choi&#39;]
ex_counter = Counter(ex_list)
print(ex_counter) #Counter({&#39;kim&#39;: 5, &#39;choi&#39;: 3, &#39;park&#39;: 2})
print(dict(ex_counter)) #{&#39;kim&#39;: 5, &#39;park&#39;: 2, &#39;choi&#39;: 3}</code></pre>
<ul>
<li><code>Counter</code> 는 자료구조는 아니지만 반복되는 데이터를 카운터하여 딕셔너리 형식으로 반환해주는 컬렉션이다.</li>
<li>더 많은 기능은 <a href="https://velog.io/@kimdukbae/Python-collections-%EB%AA%A8%EB%93%88%EC%9D%98-Counter#%EB%94%95%EC%85%94%EB%84%88%EB%A6%ACdictionary">여기</a> 참고</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[파이썬 리스트]]></title>
            <link>https://velog.io/@jae-jang/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%A6%AC%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@jae-jang/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%A6%AC%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Wed, 06 Aug 2025 07:11:34 GMT</pubDate>
            <description><![CDATA[<h3 id="1-extend">1. extend</h3>
<pre><code class="language-python">x = [1, 2, 3]
y = [4, 5]
x.extend(y)
print(x) #[1, 2, 3, 4, 5]</code></pre>
<ul>
<li>리스트의 <code>extend</code> 를 적용하면 iterable 하게 펼쳐서 데이터를 추가해준다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[SQL case]]></title>
            <link>https://velog.io/@jae-jang/SQL-case</link>
            <guid>https://velog.io/@jae-jang/SQL-case</guid>
            <pubDate>Thu, 10 Jul 2025 12:21:55 GMT</pubDate>
            <description><![CDATA[<h2 id="case-기초">CASE 기초</h2>
<h4 id="초기-테이블">초기 테이블</h4>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/1b6431c9-cc1f-4aaa-a877-a355ab181640/image.png" alt=""></p>
<h4 id="simple-ex">simple ex</h4>
<pre><code class="language-sql">SELECT CASE
            WHEN categoryid = 1 THEN &#39;음료&#39;
            WHEN categoryid = 2 THEN &#39;조미료&#39;
            ELSE &#39;기타&#39;
       END AS new_category, categoryid
FROM Products</code></pre>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/fb580355-2048-46c9-96ce-87da89096b32/image.png" alt=""></p>
<h4 id="case-with-group-by">case with group by</h4>
<pre><code class="language-sql">SELECT CASE
            WHEN categoryid = 1 THEN &#39;음료&#39;
            WHEN categoryid = 2 THEN &#39;조미료&#39;
            ELSE &#39;기타&#39;
       END AS new_category, AVG(price)
FROM Products
GROUP BY new_category</code></pre>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/f4d86df0-d402-4a6c-9000-4316963280a1/image.png" alt=""></p>
<h2 id="피보팅">피보팅</h2>
<p>피보팅은 테이블의 데이터를 row 형태로 보여줄 때 주로 사용하곤 한다. 아래 쿼리를 기반으로 한 테이블을 살펴보자.</p>
<pre><code class="language-sql">SELECT AVG(CASE WHEN categoryid = 1 THEN price ELSE NULL END) AS category1_avg_price
      ,AVG(CASE WHEN categoryid = 2 THEN price ELSE NULL END) AS category2_avg_price
      ,AVG(CASE WHEN categoryid = 3 THEN price ELSE NULL END) AS category3_avg_price
FROM Products</code></pre>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/eec41c21-1211-4eac-b5dd-b91a11427569/image.png" alt=""></p>
<ul>
<li>각각의 <code>case</code> <code>end</code> 문은 원본 <code>Products</code> 테이블에서 조건에 따른 데이터를 추출하여 <strong><code>row</code>로 보여준다</strong>는 것을 알 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Security 작동 원리]]></title>
            <link>https://velog.io/@jae-jang/Spring-Security-%EC%9E%91%EB%8F%99-%EC%9B%90%EB%A6%AC</link>
            <guid>https://velog.io/@jae-jang/Spring-Security-%EC%9E%91%EB%8F%99-%EC%9B%90%EB%A6%AC</guid>
            <pubDate>Tue, 03 Jun 2025 08:53:59 GMT</pubDate>
            <description><![CDATA[<h3 id="spring-security의-큰-틀에-대해서는-아래와-같다">spring security의 큰 틀에 대해서는 아래와 같다.</h3>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/4c5f8874-dc65-4ae8-a818-c6fcaff519ab/image.png" alt=""></p>
<ol>
<li><p>사용자가 <code>user/login</code> 등의 경로를 통해 로그인 요청을 한다.</p>
</li>
<li><p><code>UsernamePasswordAuthenticationFilter</code> 가 해당 요청을 가로챈다. 그리고 이 요청에서 사용자가 전송한 <code>id</code> <code>password</code> 를 꺼내 <code>UsernamePasswordAuthenticationToken</code> 을 만든다. 이 토큰은 단순히 <code>Spring Security</code> 프레임워크의 로그인 기능을 사용하기 위해서 생성해야 되는 객체이다.</p>
<pre><code class="language-java">public class LoginFilter extends UsernamePasswordAuthenticationFilter {

 private final AuthenticationManager authenticationManager;

 @Override
 public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

     //클라이언트 요청에서 username, password를 가로챔
     try {
         ...

         //사용자가 아이디나 비밀번호를 입력하지 않은 경우
         if (userDto.getUsername() == null || userDto.getPassword() == null) {
             ResponseUtil.setErrorResponse(INVALID_LOGIN_PARAMETER.getCode(), response, INVALID_LOGIN_PARAMETER.getMessage());
             return null;
         }

         //spring security 전용 로그인 포맷 생성
         UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDto.getUsername(),
                 userDto.getPassword(), null);
         //authenticationManager에 사용자 아이디, 비밀번호 정보 넘기기 -&gt; authManager가 정보를 userDetail에 전달한다.
         return authenticationManager.authenticate(authToken);
     } catch (IOException e) {
         throw new RuntimeException(e);
     }
 }</code></pre>
</li>
<li><p>위의 코드에서 <code>return authenticationManager.authenticate(authToken);</code> 를 실행하여 <code>AuthenticationManager</code> 에 토큰 정보를 전달한다.</p>
</li>
<li><p><code>AuthenticationManager</code> 는 토큰 정보를 토대로 어떠한 <code>Provider</code> 에게 로그인을 맡길 것인지 결정한다. 예로 들어서 아이디, 패스워드 인증이 필요한 경우에는 <code>DaoAuthenticationProvider</code> 를 선택하고, 소셜 로그인을 하는 경우 <code>OAuth</code> 방식을 사용하는 <code>OAuth2LoginAuthenticationProvider</code> 를 선택한다.</p>
</li>
</ol>
<p>5 ~ 8. 위의 과정에서 선택된 <code>Provider</code>가 <code>UserDetailsService</code> 를 호출한다. <code>UserDetailsService</code> 는 <code>DB</code> 에서 사용자 정보를 꺼내오는 역할을 한다. 그리고 이 정보를 다시 <code>Provider</code> 에 전달하고 <code>Provider</code>는 실질적인 로그인 인증을 수행한다.</p>
<pre><code class="language-java">//UserDetailsService 예제 코드
public class CustomUserDetailService implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        User userData = userRepository.findByUsername(username)
                .orElseThrow(() -&gt; new UserException(ResponseCode.INVALID_USER));

        if (userData != null) {
            return new CustomUserDetails(userData);
        }

        return null;
    }
}</code></pre>
<ul>
<li><code>return new CustomUserDetails(userData);</code> 는 <code>Provider</code> 에 사용자 정보를 넘겨주는 코드이다.</li>
</ul>
<p>9 ~ 11. 사용자 로그인에 성공하였다면, 해당 정보를 <code>SecurityContextHolder</code> 에 저장한다. 나는 JWT를 사용하기 때문에 토큰이 들어온다면, 유효성 검정을 하고 <code>SecurityContextHolder</code>에 저장하는 방식을 채택했다.</p>
<pre><code class="language-java">public class JWTFilter extends OncePerRequestFilter {

    private final JWTUtil jwtUtil;

    //토큰 인증 정보를 security context에 저장 (사용자 이름, 역할 등을 잠시 저장하려고)
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String jwt = jwtUtil.resolveToken(httpServletRequest);

        try {
            //토큰이 유효 하다면
            if (StringUtils.hasText(jwt) &amp;&amp; jwtUtil.isValidToken(jwt)) {
                String username = jwtUtil.getUsername(jwt);
                String role = jwtUtil.getRole(jwt);
                User user = new User(username, role);
                //UserDetails 에 회원 정보 객체 담기
                CustomUserDetails customUserDetails = new CustomUserDetails(user);
                //스프링 시큐리티 인증 토큰 생성
                Authentication authToken = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities());
                //세션에 사용자 등록
                SecurityContextHolder.getContext().setAuthentication(authToken);
//                filterChain.doFilter(request, response);
            }

            filterChain.doFilter(request, response); //토큰이 필요없는 경우 일수도 있으므로 doFilter 수행

        } ...
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[2차원 배열 뒤집기]]></title>
            <link>https://velog.io/@jae-jang/2%EC%B0%A8%EC%9B%90-%EB%B0%B0%EC%97%B4-%EB%92%A4%EC%A7%91%EA%B8%B0</link>
            <guid>https://velog.io/@jae-jang/2%EC%B0%A8%EC%9B%90-%EB%B0%B0%EC%97%B4-%EB%92%A4%EC%A7%91%EA%B8%B0</guid>
            <pubDate>Thu, 01 May 2025 07:08:23 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-python"># 2차원 리스트 90도 회전
def rotate_a_matrix_by_90_degree(a):

    n = len(a) # 행의 길이 
    m = len(a[0]) # 열의 길이
    result = [[0] * n for _ in range(m)]

    for i in range(n):
        for j in range(m):
            result[j][n - i - 1] = a[i][j]

    return result</code></pre>
<pre><code class="language-python">#2차원 배열 대칭
arr = arr[::-1] #상하 대칭

for i in range(n): #n은 행의 크기
    arr[i] = arr[i][::-1] #좌우 대칭</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[SSL 작동 방식]]></title>
            <link>https://velog.io/@jae-jang/SSL-%EC%9E%91%EB%8F%99-%EB%B0%A9%EC%8B%9D</link>
            <guid>https://velog.io/@jae-jang/SSL-%EC%9E%91%EB%8F%99-%EB%B0%A9%EC%8B%9D</guid>
            <pubDate>Tue, 01 Apr 2025 12:36:54 GMT</pubDate>
            <description><![CDATA[<h4 id="서론">서론</h4>
<p>사용자에게 안전한 웹을 제공하기 위해서 생각해야 할 요소는 바로 <strong>보안</strong>이다. 이때 사용자의 데이터를 주고 받을 때 암호화를 진행하는 SSL을 고려할 수 있을 것이다. SSL을 적용하면 <code>http</code> -&gt; <code>https</code> 가 되며, 주고받는 데이터에 대해서 암호화가 진행된다. 구체적으로 이러한 SSL 인증서를 <code>CA</code> 라는 신뢰할 수 있는 구간에서 발급을 받으며, 이 발급된 인증서를 서버에 삽입하면 된다.</p>
<h3 id="ssl-구동-원리">SSL 구동 원리</h3>
<img src="https://velog.velcdn.com/images/jae-jang/post/ee37f72d-ba57-450c-939b-8cdc0df42b52/image.png" width="80%" height="n%">

<ul>
<li><p>사용자가 크롬 웹 브라우저를 이용해서 특정 웹 사이트에 접속한다고 가정해보자.</p>
</li>
<li><p>그러면 사용자가 특정 웹 브라우저를 클릭했을때 먼저 크롬 브라우저가 해당 웹 사이트가 SSL 인증서가 있는지 확인하다.</p>
</li>
<li><p>그 후에 만약 SSL 인증서가 있으면 안전하다는 OK 사인을 사용자에게 보낸다.</p>
<img src="https://velog.velcdn.com/images/jae-jang/post/fbed83c2-aab3-4097-8445-d013defb58e8/image.png" width="60%" height="50%">
</li>
<li><p>사용자는 서버에게 공개키를 우선 요청한다. 그러고 나면 서버는 사용자에게 공개키를 전달한다.</p>
</li>
<li><p>사용자는 전달 받은 공개키를 기반으로, 대칭키를 암호화하여 생성하고 서버에 전송한다.</p>
</li>
<li><p>이 암호화된 대칭키를 전달 받은 서버는 본인의 비밀키를 이용하여 복호화하고 대칭키를 얻는다.</p>
</li>
<li><p>사용자의 세션이 끊길때까지 초기에 서로 주고 받은 대칭키로 데이터를 주고 받게 된다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[XSS CSRF]]></title>
            <link>https://velog.io/@jae-jang/XSS-CSRF</link>
            <guid>https://velog.io/@jae-jang/XSS-CSRF</guid>
            <pubDate>Thu, 27 Mar 2025 04:12:48 GMT</pubDate>
            <description><![CDATA[<h2 id="xss">XSS</h2>
<p>XSS는 <strong>웹 게시판이나 메일 등에 자바 스크립트와 같은 스크립트 코드를 삽입</strong>해 개발자가 고려하지 않은 기능이 작동하게 하는 공격이다. 주로 사용자의 로그인 상태를 기록하기 위해, 쿠키나 로컬 스토리지에 정보를 저장하는데, 이를 가져올 수 있다.</p>
<h4 id="xss-절차">XSS 절차</h4>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/1bb73566-65d8-49a3-b5c0-a8d4f17d0d96/image.png" alt=""></p>
<ul>
<li><p>해커가 웹사이트의 게시판에 악의적인 스크립트를 삽입</p>
</li>
<li><p>사용자가 해커의 스크립트가 삽입된 게시판을 읽음</p>
</li>
<li><p>사용자의 정보가 해커로 유출됨</p>
</li>
</ul>
<h4 id="xss-해결방법">XSS 해결방법</h4>
<ol>
<li><p>만약에 쿠키에 정보를 저장한다면 HttpOnly를 설정한다. (xss에는 방어가 완벽하게 되지만, csrf에 취약해짐)</p>
</li>
<li><p>근본적으로 xss 공격은 입력값에 대한 검증이 제대로 이루어지지 않아 발생하는 취약점이다. 따라서 사용자의 모든 입력값에 대하여 필터링을 해주어야 한다. ex) &lt;, &gt;, &quot;, &#39;등 주로 스크립트를 실행하기 위한 특수문자를 필터링 한다.</p>
</li>
</ol>
<h2 id="csrf">CSRF</h2>
<p>CSRF는 사용자의 권한을 악용하여 공격자가 원하지 않는 요청을 보내는 것이다. 구체적으로 <strong>로그인된 사용자를 공격자가 의도한 행위</strong> 특정 웹사이트에 요청하게 한다.</p>
<h4 id="csrf-3가지-조건">CSRF 3가지 조건</h4>
<blockquote>
<p>CSRF가 성공하려면, 아래 <strong>3가지 조건</strong>이 만족되어야 한다.</p>
</blockquote>
<ol>
<li>사용자는 보안이 취약한 서버로부터 <strong>이미 로그인되어 있는 상태</strong>여야 한다.</li>
<li><strong><ins>쿠키 기반의 서버 세션 정보를 획득</strong>할 수 있어야 한다.</li>
<li>공격자는 <strong>서버를 공격하기 위한 요청 방법에 대해 미리 파악</strong>하고 있어야 한다. (사용자 패스워드 변경은 어떤 URI와 메소드를 사용하는지)</li>
</ol>
<h4 id="csrf-절차">CSRF 절차</h4>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/a5af929e-fb7f-4aaa-b1f5-0114ceff0184/image.png" alt=""></p>
<ul>
<li><p>사용자는 웹 사이트에 로그인을 한다.</p>
</li>
<li><p>로그인이 성공하면 (로그인 관리 방식이 세션이라는 가정하에) <strong><ins>사용자의 브라우저에 쿠키 정보가 저장</strong>된다.</p>
</li>
<li><p>공격자의 피싱 메일 혹은 악성 스크립트가 작성된 페이지 링크를 열람 한다면, 악성 페이지에 이동하게 된다.</p>
</li>
<li><p>악성 사이트에서 특정 웹 사이트에 <strong>사용자의 쿠키 정보를 가지고 위조된 요청 정보를 전송</strong>한다.</p>
</li>
<li><p>이로 인해 사용자는 패스워드 변경 등과 같은 의도하지 않은 행동을 수행하게 된다.</p>
</li>
</ul>
<h4 id="csrf-방지">CSRF 방지</h4>
<p><strong><ins>가장 단순한 방법은 JWT 토큰을 사용</strong>하는 것이다. 로그인을 성공하면 사용자에게 JWT 토큰을 발급하고, 해당 토큰을 쿠키가 아닌, 로컬 스토리지에 저장하면 CSRF를 방지할 수 있다. 하지만 <strong>XSS 공격에 토큰이 탈취</strong>당할 수 있다. 따라서 토큰을 발급할 때 <strong>ACCESS, REFRESH 두 중류의 토큰</strong>을 발급한다. </p>
<h3 id="🗒️ref">🗒️REF</h3>
<p><a href="https://devscb.tistory.com/123">https://devscb.tistory.com/123</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[도커 compose]]></title>
            <link>https://velog.io/@jae-jang/%EB%8F%84%EC%BB%A4-compose</link>
            <guid>https://velog.io/@jae-jang/%EB%8F%84%EC%BB%A4-compose</guid>
            <pubDate>Tue, 28 Jan 2025 12:29:00 GMT</pubDate>
            <description><![CDATA[<p>사실 도커의 모든 CLI 명령어는 <code>compose</code> 로 바꿀 수 있다. 개인적으로 도커를 사용할 때는 항상 <code>compose</code> 를 사용할 것 같다. 그만큼 굉장히 편리한 기능이다. 왜냐하면 <code>compose</code> 는 여러개의 컨테이너를 하나로 묶어서 관리할 수 있는 서비스를 제공한다. 또한 복잡한 명령어를 단순화 시켜주는 강력한 기능을 제공한다.</p>
<p>아래는 mysql 이미지를 이용해서 컨테이너를 띄우는 명령어이다. 도커 허브의 공식 문서를 참조했다.</p>
<pre><code class="language-shell">docker run -e MYSQL_ROOT_PASSWORD=password1234 -p 3306:3306 {host}:/var/lib/mysql -d mysql</code></pre>
<ul>
<li>위의 명령어를 계속 사용하다 보면 <code>Mysql</code> 컨테이너를 띄우기가 매~우 귀찮아진다. 처음에는 괜찮을 지도 모르겠지만, 컨테이너를 내리고 올리기를 반복하면 저렇게 긴 명령어를 계속 치다보면.. 상당히 힘들다.</li>
</ul>
<p>그러니까 <code>compose</code> 기능을 사용하자. 위의 명령어와 동일한 기능을 하게끔 <code>compose.yml</code> 파일을 아래와 같이 작성해준다.</p>
<pre><code class="language-yml">services:
  my-db:
    image: mysql
    environment:
      MYSQL_ROOT_PASSWORD: pwd1234
      MYSQL_DATABASE: mydb # MySQL 최초 실행 시 mydb라는 데이터베이스를 생성해준다.
    volumes:
      - ./mysql_data:/var/lib/mysql
    ports:
      - 3306:3306</code></pre>
<ul>
<li>파일 작성후 <code>docker compose up -d</code> 를 작성하면 끝이다. 이는 위의 긴 명령어와 완전히 동일한 기능을 한다. </li>
</ul>
]]></description>
        </item>
    </channel>
</rss>