저번 글에서는 인덱스 탐색 과정에 대해 간단하게 설명했습니다.

그리고 인덱스 탐색을 어떻게 표현하지는도 살펴봤습니다.

이번에는 실제 MySQL에서 인덱스 관련된 실행 게획을 어떻게 표현하는지 살펴보도록 하겠습니다.

 

 

1. MySQL의 인덱스 실행 계획: EXPLAIN FORMAT = TREE

EXPLAIN FORMAT = TREE로 지정했을 때 인덱스 관련 동작이 어떻게 표현되는지 알아보겠습니다.

이번 글에서는 EXPLAIN FORMAT = TREE 기준을 설명하고, 다음 글에서는 고전적인 실행게획에서 인덱스 관련 동작이 표시되는 방법을 설명할 예정입니다.

 

아래와 같이 SQL을 실행합니다. 아래 SQL은 ORD_DT에는 범위 조건을 사용했습니다.(범위 조건을 사용해 2017년 1월 4일 하루만 조회합니다.)

앞에서 T_ORD_BIG 테이블의 ORD_DT 컬럼에 대해서는 T_ORD_BIG_X1 인덱스를 생성했습니다. 그러므로 아래 SQL은 해당 인덱스를 사용해 처리가 될 확률이 높습니다. 실행계획을 살펴보면, 가장 아래에 'Index range scan'이 표시되어 있습니다. 그리고 'using T_ORD_BIG_X1'이라고 어떤 인덱스를 사용했는지 친절하게 표시되어 있습니다. MySQL이 버젼이 올라갈수록, 실행계획을 해석하기 좋게 보여주고 있습니다.

EXPLAIN FORMAT = TREE
SELECT  COUNT(*)
FROM    MYTUNDB.T_ORD_BIG T1
WHERE   T1.ORD_DT >= STR_TO_DATE('20170104','%Y%m%d')
AND     T1.ORD_DT < STR_TO_DATE('20170105','%Y%m%d');

-> Aggregate: count(0)
     -> Filter: ((mytundb.t1.ORD_DT >= <cache>(str_to_date('20170104','%Y%m%d'))) and (mytundb.t1.ORD_DT < <cache>(str_to_date('20170105','%Y%m%d'))))  (cost=182.54 rows=900)
         -> Index range scan on T1 using T_ORD_BIG_X1  (cost=182.54 rows=900)

 

 

이번에는 ORD_DT에 범위 조건이 아닌, 같다(=) 조건을 사용한 SQL을 실행해봅니다. (위 SQL과 똑같이 2017년 1월 4일 하루만 조회합니다.) 아래와 같슽니다. 실행계획을 살펴보면 'Index range scan'이 사라지고, 'Index lookup'이 표시되었습니다.

EXPLAIN FORMAT = TREE
SELECT  COUNT(*)
FROM    MYTUNDB.T_ORD_BIG T1
WHERE   T1.ORD_DT = STR_TO_DATE('20170104','%Y%m%d');

-> Aggregate: count(0)
     -> Index lookup on T1 using T_ORD_BIG_X1 (ORD_DT=str_to_date('20170104','%Y%m%d'))  (cost=92.53 rows=900)

 

'Index lookup' 역시 'Index range scan' 처럼 수직점 탐색과 수평적 탐색이 발생합니다. 그러므로 결국 실제 작동하는 내용은 Index range scan과 같다고 보시면 됩니다.

MySQL은 인덱스를 사용하는 상황에서, 사용된 조건이 범위(>=, <=, BETWEEN) 조건이면 Index range scan을, 같다(=) 조건이면 Index lookup으로 실행계획을 표시해줍니다.

 

이번에는 아래와 같이 SQL을 실행합니다. WHERE 절에서 ORD_DT 컬럼을 DATE_FORMAT 처리했기 때문에 인덱스를 '효율적'으로 사용 못하는 경우입니다.(인덱스를 사용은 하지만 비효율적인 방법으로 사용하게 됩니다.) 실행계획을 보면 'Index scan'이라고 표시되어 있습니다. 이 부분만 보면, '인덱스를 탔네'라고 생각할 수 있습니다. 하지만, 아래 경우는 인덱스의 리프 노드를 모두 읽어서 조건을 처리한 경우입니다. 

EXPLAIN FORMAT = TREE
SELECT  COUNT(*)
FROM    MYTUNDB.T_ORD_BIG T1
WHERE   DATE_FORMAT(T1.ORD_DT,'%Y%m%d') = '20170104'

-> Aggregate: count(0)
     -> Filter: (date_format(mytundb.t1.ORD_DT,'%Y%m%d') = '20170104')  (cost=31559.80 rows=301158)
         -> Index scan on T1 using T_ORD_BIG_X1  (cost=31559.80 rows=301158)

 

 

방금 살펴본, SQL 세 개를 EXPLAIN ANALYZE를 사용해 각각 실행해봅니다. 아래와 같은 실제 실행 계획을 확인할 수 있습니다.

1. 범위 조건 ANALYZE
 -> Aggregate: count(0)  (actual time=0.462..0.462 rows=1 loops=1)
     -> Filter: ((mytundb.t1.ORD_DT >= <cache>(str_to_date('20170104','%Y%m%d'))) and (mytundb.t1.ORD_DT < <cache>(str_to_date('20170105','%Y%m%d'))))  (cost=180.64 rows=900) (actual time=0.034..0.407 rows=900 loops=1)
         -> Index range scan on T1 using T_ORD_BIG_X1  (cost=180.64 rows=900) (actual time=0.032..0.241 rows=900 loops=1)

2. 같다(=) 조건 ANALYZE 
-> Aggregate: count(0)  (actual time=0.383..0.383 rows=1 loops=1)
     -> Index lookup on T1 using T_ORD_BIG_X1 (ORD_DT=str_to_date('20170104','%Y%m%d'))  (cost=90.63 rows=900) (actual time=0.042..0.316 rows=900 loops=1)
 
3. 컬럼변경(DATE_FORMAT) ANALYZE 
-> Aggregate: count(0)  (actual time=200.378..200.378 rows=1 loops=1)
     -> Filter: (date_format(mytundb.t1.ORD_DT,'%Y%m%d') = '20170104')  (cost=31559.80 rows=301158) (actual time=1.075..200.319 rows=900 loops=1)
         -> Index scan on T1 using T_ORD_BIG_X1  (cost=31559.80 rows=301158) (actual time=0.108..85.289 rows=304700 loops=1)

위 내용을 보면 '1. 범위 조건' 보다 '2. 같다(=) 조건'이 아주 아주 약간 더 빠릅니다.(각 실행계획 가장 위의 actual time) 거의 사람이 차이를 느낄 수 없는 속도일듯합니다. 실제 실행해 보면 1번과 2번 모두 0.000초 정도입니다. 다만, '1. 범위 조건'의 경우는 Filter 라는 단계가 실행계획 중간에 끼어 들어옵니다. '3. 컬럼 변경'의 경우만 0.2초 정도의 시간이 걸립니다. 

 

정리하면 아래와 같습니다.

  • 범위 조건을 인덱스로 처리하면, 실행 계획에 Index range scan 으로 표시된다.
  • 같다 조건을 인덱스로 처리하면, 실행 계획에 Index lookup 으로 표시된다.
  • Index range scan과 Index lookup은 수직적 탐색과 수평적 탐색이 발생하는 같은 작업이라고 볼 수 있습니다.
  • Index scan은 인덱스 리프 노드를 순차적으로 모두 읽는 작업이다.
  • 당연히, Index scan은 Index range scan이나 Index lookup 보다 성능이 좋지 못할 수 있다.
  • Index scan은 오라클의 Index full scan이다.
  • Index scan이 나왔다고 인덱스를 '효율적'으로 사용했다고 착각하지 말자.!

 

 

이정도일거 같습니다. 감사합니다.

 

 

앞에서 클러스터드 인덱스와 넌클러스터드 인덱스의 차이점에 대해서 설명했습니다.
잠시, 클러스터드와 넌클러스터드의 차이는 잠시 접어두고 인덱스를 이용해 데이터 찾는 과정을 살펴보겠습니다.

 

1. 인덱스 탐색

B Tree(B+Tree) 인덱스에서 조건에 맞는 값을 찾기 위해서는 두 단계의 과정으로 인덱스를 탐색합니다.

  • 1 단계: 루트 노드에서 리프 노드로 내려가기(수직적 탐색)
  • 2 단계: 리프 노드를 순차적으로 읽어 가기(수평적 탐색)

위의 과정을 그림으로 살펴보면 아래와 같습니다. 그림의 빨간색 화살표가 1 단계인 수직적 탐색이고, 파란색 화살표가 2 단계인 수평적 탐색입니다.

경우에 따라 2 단계는 생략될 수도 있습니다. 찾고자 하는 값이 한 건이거나, 극히 일부라면 2 단계 과정인 수평적 탐색은 생략될 수도 있습니다. (1 단계인 수직적 탐색으로 찾은 곳에 원하는 데이터가 모두 있다면, 수평적 탐색은 필요가 없는 경우죠.)

 

위 그림을 더 간략하게 그려보면 아래와 같습니다. 인덱스 동작 관련해서 자주 보게될 그림입니다.

 

 

방금 살펴본 설명과 그림이 바로, 우리가 흔이 말하는 '인덱스를 탔네'라고 말하는 것의 동작 방식입니다.
오라클의 실행 계획에서는 이와 같은 동작을 'INDEX RANGE SCAN'이라고 표현합니다.
반면에 MySQL에서는 실행 계획을 보는 방식과 상황에 따라 다양하게 표현이 됩니다. 관련해서는 다음 글에서 설명을 드릴 예정입니다.

 

위와  같은 인덱스 탐색 외에도, 인덱스 리프 노드를 차례대로 모두 읽어야 하는 경우가 있습니다. 해당 경우는 아래 그림과  같이 표시합니다. 오라클에서는 INDEX FULL SCAN이라고 동작하는 경우입니다.

 

 

오늘은 여기까지입니다. 감사합니다.

앞에서 아나콘다 가상 환경을 설정했습니다.

 

Anaconda 가상 환경 만들기

파이썬은 다양한 라이브러리(모듈 또는 패키지)를 사용할 수 있는 것이 장점입니다. 하지만, 라이브러리 버젼이 너무 많고 다양하다 보니, 여러 프로젝트에 하나의 환경을 사용하다 보면, 문제

sweetquant.tistory.com

 

아나콘다 가상 환경을 사용하는 프로젝트에서 파이썬 라이브러리를 추가해서 사용하기 위해서는

아나콘다 가상 환경에 진입해 라이브러리를 추가하애 합니다.

우선, 위의 글에서처럼, 'Anaconda Prompt'를 실행합니다.

그 다음에 conda env list 를 실행해 가상 환경 리스트를 조회합니다.

conda env list

 

위 결과를 통해 어떤 가상 환경들이 있는지 확인할 수 있습니다. 조회된 가상 환경 중에 우리가 만들었던 python37_64bit_mystktest 가상 환경에 진입해봅니다. 아래와 같이 conda activate를 실행하면 됩니다.

conda activate python37_64bit_mystktest

 

가상 환경인 python37_64bit_mystktest에 진입했으므로, pip를 이용해 필요한 라이브러리(모듈, 패키지)을 설치하면 됩니다. 여기서는 requests를 설치해봅니다.

pip install requests

 

아나콘다 가상 환경에 라이브러리 설치하는 과정을 살펴봤습니다. 이상입니다.

이번에는 클러스터드 인덱스의 구조를 살펴볼 차례입니다. 넌클러스터드와 클러스터 인덱스의 다음 차이점을 반드시 기억하시고 읽어주시기 바랍니다.

  • 넌클러스터드 인덱스: 리프 노드에 실제 데이터를 찾아 갈 수 있는 '주소 값'이나 '키 값'이 저장된 인덱스
  • 클러스터드 인덱스: 리프 노드에 실제 '데이터'가 저장된 인덱스

 

2. Clustered Index

T_ORD_BIG의 클러스터드 인덱스는 Primary 인덱스입니다.
앞에 글에서도 'SHOW INDEX FROM T_ORD_BIG' 을 실행했을 때, 'Key_name'이 'Primary'로 되어 있는 인덱스가 바로 클러스터드 인덱스라고 설명했습니다.

 

여기서 잠깐, MySQL에서는 인덱스라는 용어를 Key라는 용어와 혼용해서 사용합니다.

그러므로 'SHOW INDEX FROM'을 실행하면 인덱스의 이름 항목이 'Index_name'이 아닌 'Key_name'으로 나옵니다.

 

T_ORD_BIG의 클러스터드(Primary) 인덱스는 PK(Primary Key) 컬럼인 ORD_SEQ로 구성되어 있습니다.

MySQL의 InnoDB 스토리지 엔진에서, 테이블의 PK 컬럼은 무조건 클러스터드 인덱스로 만들어집니다.
다시 말해, '테이블 생성 = 클러스터드 인덱스 생성'이 됩니다. 우리는 테이블을 만들지만, 테이블의 데이터는 클러스터드 인덱스 구조로 관리가 되는겁니다.
아무리, 클러스터드 구조로 테이블을 만들기 싫어도 방법이 없습니다.

클러스터드 인덱스를 피하기 위해 PK를 설정하지 않아도, 내부적으로 6 byte의 Hidden 컬럼을 만들어 PK를 구성한다고 합니다.

(구글링을 해보면, 이와 같은 이야기가 나오는데, MySQL 공식 레퍼런스에서는 이와 같은 부분을 찾을수가 없네요. 혹시 MySQL 레퍼런스에서 보신분 있으면 알려주세요!^^)

 

T_ORD_BIG의 클러스터드 인덱스를 그려보면 아래 그림과 같습니다. 처음에 이야기했듯이, 인덱스의 리프 노드에는 실제 데이터가 저장되어 있는 것을 알 수 있습니다.

 

클러스터드 인덱스의 리프 노드에는 실제 데이터가 저장되어 있으므로, 테이블 별로 하나만 만들 수 있습니다. 넌 클러스터드는 하나의 테이블에 여러개 만들 수 있지만, 클러스터드는 무조건 하나만 만들 수 있으므로 클러스터할 컬럼 선택에 신중한 선택이 필요합니다. 하지만 MySQL은 무조건 PK 컬럼에 클러스터드를 설정하므로 선택의 폭이 제한적입니다.

 

클러스터드 인덱스와 같은 개념으로 오라클에는 IOT가 있습니다. 오라클의 IOT는 선택이라면, MySQL의 클러스터드 인덱스는 강제적입니다.

MS-SQL Server에도 클러스터드 인데스가 있습니다. MS-SQL Server에서 클러스터드 인덱스는 PK 컬럼이 아닌 곳에도 설정할 수 있습니다. 반면에 MySQL에서는 무조건 PK 컬럼에만 클러스터드 인덱스가 설정이 됩니다.

이와 같은 특징을 잘 이해하고 MySQL의 테이블을 설계할 필요가 있습니다.

 

 


넌클러스터드(Non-Clustered) 인덱스와 클러스터드(Clustered) 인덱스에 대해 알아볼 차례입니다. MySQL의 인덱스와 관련된 성능에서 가장 중요한 부분이 아닐까 싶습니다.

앞에서 B Tree 인덱스에 대해 설명드렸는데, B Tree는 인덱스를 만들때 사용하는 기본적인 자료구조입니다.
MySQL에서는(정확히는 MySQL의 InnoDB 스토리지 엔진) B Tree 인덱스를 넌클러스터드와 클러스터드로 세분화해서 사용할 수 있습니다. 넌클러스터드와 클러스터드는 리프 노드의 구성에 따라 구분됩니다. 아래와 같습니다.

  • 넌클러스터드 인덱스: 리프 노드에 실제 데이터를 찾아 갈 수 있는 '주소 값'이나 '키 값'이 저장된 인덱스
  • 클러스터드 인덱스: 리프 노드에 실제 '데이터'가 저장된 인덱스

 

1. Non Clustered Index

우선은 넌클러스터드인덱스를 설명하고, 다음 글에서 클러스터드 인덱스에 대해 살펴볼 예정입니다.
앞의 글에서 T_ORD_BIG의 ORD_DT 컬럼에 대해 인덱스를 생성했습니다. 이 인덱스는 넌클러스터드 인덱스입니다.

 

[MySQL튜닝]인덱스를 만들어 보자

테스트를 위한 DB 구성은 아래 글을 참고해 구성할 수 있습니다. https://sweetquant.tistory.com/m/346 RDBMS의 SQL 성능을 좌우하는 요소 세 가지를 뽑아보자면 아래와 같습니다. SQL문 자체 인덱스(INDEX) 통..

sweetquant.tistory.com

 

아래와 같이 SHOW INDEX FROM 을 사용해 현재 테이블에 생성된 인덱스를 확인할 수 있습니다.

SHOW INDEX FROM MYTUNDB.T_ORD_BIG;

위 SQL을 실행해 결과를 확인해 보면, T_ORD_BIG_X1 인덱스가 넌클러스터드인지, 클러스터드인지 구분되어 있지는 않습니다. 위 SQL 결과 중, Key_name 항목의 결과가 Primary면 클러스터드 인덱스이고 Primary가 아니면 모두 넌 클러스터드 인덱스로 생각하시면 됩니다.

 

MySQL에서는 인덱스를 생성할 때 클러스터드와 넌클러스터드를 지정할 수 없습니다.

테이블을 생성할 때 지정한 Primary Key 외에 추가로 생성하는 인덱스는 모두 넌클러스터드 인덱스 입니다.

T_ORD_BIG의 ORD_DT에 대한 인덱스를 그림으로 표현해보면 아래와 같습니다.

 

위 그림에서, 가장 아래 리프 노드를 보면, 인덱스 컬럼인 ORD_DT외에 ORD_SEQ 정보도 존재합니다.
ORD_SEQ는 T_ORD_BIG의 PK(Primary Key) 컬럼으로서 클러스터드 인덱스로 구성된 컬럼입니다.
이처럼 MySQL의 넌클러스터드 인덱스의 리프 노드에는 해당 인덱스의 컬럼과 함께 PK의 컬럼 값이 존재합니다.
(PK 컬럼 값은 실제 데이터를 찾기 위한 '키 값'으로 사용됩니다.)
반면에 오라클의 넌클러스터드 인덱스의 리프 노드에는 실제 데이터를 바로 찾아 갈 수 있는 주소 값(ROWID)이 저장되어 있습니다.

 

오늘은 여기까지입니다. 감사합니다.

 

 

 

Trade Optimizer(트레이드 최적화기)

주식 매매 또는 투자에 최적화된 방법을 찾기 위한 개인적인 프로젝트입니다.

MySQL, Python(Anaconda + Pycharm)을 결합한 데이터 분석 프로젝트로서 천천히 준비중입니다.

Trade Optimizer에서는 아래와 같은 글들을 올립니다.

  • Trade Optimizer를 구축해가는 과정
  • 파이썬 데이터 수집 기술
  • 파이썬 기초 기술
  • 파이썬, SQL 데이터 처리 기술
  • 파이썬, SQL 데이터 분석 기술
  • 트레이드 최적화된 결과
    • 특정 종목을 추천하거나 투자/매매를 권장하기 위한 글이 아닙니다.
    • 개인적으로 분석한 주식 종목을 기록 차원에서 올릴 예정입니다.
    • 본 카테고리 글을 참고해 투자한 경우, 결과에 대해서는 그 누구도 절대 책임지지 않습니다.

본 글에서는 Anaconda와 Pycharm 조합으로 파이썬을 사용합니다.

 

기초 단계라 할 수 있는 Anaconda 및 Pycharm 설치, 'Hello Python' 실행해보기 까지는 아래 글을 참고해주세요.

 

기본적인 환경(Anaconda 설치, Pycharm 설치, Anaconda 가상환경, Pycharm 신규 프로젝트)을 완료했다면, 이제 간단한 'Hello Python'을 하나 만들어 보겠습니다.

앞에서 만든 MyStkTest 프로젝트를 엽니다. 아래와 같은 순서로 새로운 파이썬 파일을 생성합니다.

  • 왼쪽의 Project 창의 MyStkTest에서 마우스 우클릭
  • 팝업 메뉴에서 New 선택
  • 팝업 메뉴에서 Python file 선태
  • New Python file 창에 '000_HelloPython' 이라고 입력하고 엔터를 누릅니다.

 

HelloPython 파일이 만들어졌으면, 해당 파일에 간단한 Print 코딩을 작성합니다. 아래 그림을 참고해서 진행하시면 됩니다.

  • 000_HelloPyhon.py 선택
  • 코딩 창에 아래 내용을 입력
    • print('Hello Python')
  • 코딩 창, 빈 공간에 마우스 우클릭하면 팝어 메뉴가 나옵니다.
    • 팝업 메뉴에서 Run '000_HelloPyhon' 을 선택
  • 아래쪽 창에 결과가 나온 것을 확인할 수 있습니다.

 

아주 간단한 파이썬 프로그램을 만들어 실행해봤습니다.

 

 

 

 

Pycharm에서 'MyStkTest'라는 신규 프로젝트를 만들어보겠습니다.

윈도우즈에서 Jetbrains의 Pycharm을 찾아 실행합니다. 아래 그림을 참고합니다.

 

아래와 같은 Welcome 창이 나오면, New Project를 선택해주세요.

 

아래 그림과 같은 New Project 창이 나오면, 아래 순서대로 작업을 진행합니다.

  • Location에 'MyStkTest' 를 입력
  • Previsoulsy configured interpreter를 선택
  • Add Interpreter 클릭 / Add local Inpterpreter 선택
    • 여기서, 앞에서 만든 conda 가상 환경을 설정합니다.

 

Add Local Interpreter에서 아래와 같이 앞에서 설정한 가상 환경을 선택하고 'OK'를 누릅니다.

 

아래와 같이 환경이 설정되었으면, 'Create'를 클릭해 MyStkTest 프로젝트를 생성합니다.

여기까지 진행하면, 코딩을 위한 기본적인 준비가 끝났네요.

 

 

 

 

 

 

 

 

파이썬은 다양한 라이브러리(모듈 또는 패키지)를 사용할 수 있는 것이 장점입니다.

하지만, 라이브러리 버젼이 너무 많고 다양하다 보니, 여러 프로젝트에 하나의 환경을 사용하다 보면, 문제가 발생할 가능성이 있습니다. 그러므로 프로젝트별로 하나의 가상 환경을 만들어 처리하는 것이 좋습니다.

본 글은 아래 내용을 참고했습니다.

  - https://hyunlee103.tistory.com/58

 

Anaconda + 파이참(Pycharm), 가상환경 구축하고 연동하기

맨날 colab에서만 코딩을 하다가 파일이 많아지고 정리가 어려워 IDE를 사용하기로 했다. colab은 도커 환경이라 내가 따로 환경 설정을 해주지 않아도 되고 필요한 패키지들도 거의 다 설치되어 있

hyunlee103.tistory.com

 

1. Anaconda Prompt 실행

가상 환경을 생성하기 위해 아나콘다 프롬프트를 실행합니다.

  - 설치된 프로그램 중에 Anacond3 (64-bit)Anaconda Prompt (Anaconda 3) 을 실행(아래 그림 참고)

 

 

2. Anaconda Prompt에서 가상 환경 생성

아래와 같이 입력해 'python37_64bit_mystktest' 라는 가상 환경을 생성합니다. 파이썬 버젼을 3.7을 사용하도록 설정했습니다.

  - conda create -n python37_64bit_mystktest python=3.7

  - 아래와 같이 나오면 y를 눌러주시면 됩니다.

 

 

3. 가상 환경 관련 prompt 명령어

Anaconda prompt에서 아래와 같은 명령어들을 사용할 수 있습니다.

  • conda env list : 콘다 가상환경 리스트 확인
  • conda activate python37_64bit_mystktest : 가상환경 진입
  • conda deactivate : 콘다 가상환경 빠지기

여기까지입니다. 다음에는 파이참에서 가상환경을 사용해 프로젝트를 만들어보도록 하겠습니다.

 

 

 

파이썬 개발 툴 중 하나인 Pycharm을 설치합니다.

앞에서 설치한 Anaconda와 연동해서 사용하게 됩니다.

  - https://sweetquant.tistory.com/354?category=1314904 

 

아래 경로에서 무료로 사용할 수 있는 커뮤니티 에디션을 다운로드합니다.(아래 경로는 윈도우즈용입니다.)

  - https://www.jetbrains.com/ko-kr/pycharm/download/#section=windows

 

다운로드 PyCharm: JetBrains가 만든 전문 개발자용 Python IDE

 

www.jetbrains.com

 

다운로드 받은 아래 파일을 실행합니다.(다운로드 시점에 따라 버젼과 파일명은 다를 수 있습니다.)

  - pycharm-community-2022.2.3

 

파이참 설치 역시 아나콘다처럼 대부분 Next만 눌러주시면 됩니다. 특별히 어려운거 없으므로 넘어갑니다.

 

 

 

 

파이썬을 사용해 주식 데이터를 수집 및 분석하기 위해 아나콘다, 파이참, MySQL 설치가 필요합니다.

 

우선은 아나콘다를 설치합니다. 다음 경로에서 아나콘다를 다운로드합니다.

 - https://www.anaconda.com/products/distribution

 

Anaconda | Anaconda Distribution

Anaconda's open-source Distribution is the easiest way to perform Python/R data science and machine learning on a single machine.

www.anaconda.com

 

 

예전에는 윈도우즈용은 32bit, 64bit 버젼이 별도로 있었으나,현재는 64bit에서 32bit를 하위호환하도록 구성되어 있는거 같습니다. 키움API를 위해서는 32bit용이 필요합니다. 이 경우 64bit의 아나콘다에서 별도 가상 환경 설정이 필요합니다. 아래 글을 참고해주세요.

  - https://hyunlee103.tistory.com/58

 

Anaconda + 파이참(Pycharm), 가상환경 구축하고 연동하기

맨날 colab에서만 코딩을 하다가 파일이 많아지고 정리가 어려워 IDE를 사용하기로 했다. colab은 도커 환경이라 내가 따로 환경 설정을 해주지 않아도 되고 필요한 패키지들도 거의 다 설치되어 있

hyunlee103.tistory.com

 

윈도우즈용 최신 버젼을 다운로드합니다. 저 같은 경우 2022.10 버젼을 받아서 설치했습니다. 아래 파일을 받아서 실행합니다.

  - Anaconda3-2022.10-Windows-x86_64

 

별다른 거 없이 Next(다음) 버튼만 누르면 설치가 완료됩니다.

 

 

 

 

MySQL의 인덱스 종류와 B Tree 인덱스에 대해 알아보도록 하겠습니다.

 

1. MySQL의 인덱스 종류

MySQL은 아래와 같은 구조의 인덱스를 제공합니다. 정확히는 MySQL의 스토리지 엔진중 하나인 InnoDB에서 사용할 수 있는 인덱스입니다. MySQL에는 InnoDB 외에도 다양한 스토리지 엔진도 사용할 수 있지만, 대부분 InnoDB를 사용합니다.

  • B Tree 인덱스: 원하는 데이터를 빠르게 검색하기 위한 인덱스
  • R Tree 인덱스(Spatial Index): 위치나 거리등의 검색에 효율적인 인덱스
  • Full text search 인덱스: 문자열 검색에 효율적인 인덱스


RDBMS에서 흔이 말하는 '인덱스' 대부분은 B Tree 구조의 인덱스입니다. (이 글에서도 특별히 언급하지 않으면 B Tree 인덱스를 뜻합니다.) 마찬가지로, 인덱스를 만들때 특별히 옵션을 주지 않으면 B Tree 구조의 인덱스가 만들어집니다.
앞에 글(인덱스 왜 빨라)에서 예를 든 구조(거래처 서류와 서류함) 역시 B Tree 인덱스입니다.

  - https://sweetquant.tistory.com/352

 

[MySQL튜닝]인덱스 왜 빨라?

'인덱스를 만들면 조회가 빨라지더라~'라는 것에 대해서는 이미 앞에서 살짝 테스트를 해봤습니다. 인덱스를 마스터하려면 인덱스의 종류, 구조등 다양한 내용을 공부해야 합니다. 이와 같은 복

sweetquant.tistory.com


실제 RDBMS에서 사용하는 인덱스 구조는 B Tree를 좀 더 발전시킨 B* Tree 또는 B+ Tree 입니다. B Tree, B* Tree, B+ Tree에 대한 차이는 아래 잘 정리된 블로그 글로 대신하도록 하겠습니다. 한 번쯤 읽어보시고 각자 정리해보시기 바랍니다. (사실 아래 글만으로 정리가 안될 수도 있습니다. 대략적인 차이를 이해하는 정도에서 넘어가시는게 좋습니다.)

  - https://wiper2019.tistory.com/301

  - https://ssocoit.tistory.com/217

 

아래의 MySQL 레퍼런스에 의하면, MySQL은 B Tree 구조를 사용한다고 설명되어 있습니다. B+나 B*를 직접적으로 언급하는 부분은 없는거 같습니다. 다만, 많은 책들과 자료에서 MySQL의 인덱스는 B+ Tree라고 설명되어 있습니다.(저 역시 B+ Tree가 맞다고 생각합니다.)

  - https://dev.mysql.com/doc/refman/8.0/en/innodb-physical-structure.html

 

MySQL :: MySQL 8.0 Reference Manual :: 15.6.2.2 The Physical Structure of an InnoDB Index

15.6.2.2 The Physical Structure of an InnoDB Index With the exception of spatial indexes, InnoDB indexes are B-tree data structures. Spatial indexes use R-trees, which are specialized data structures for indexing multi-dimensional data. Index records are

dev.mysql.com

 

2. B Tree 인덱스의 세부 명칭

아래 그림을 통해 B Tree(B+ Tree)를 구성하는 요소별 명칭을 정리해봅니다. 아래 그림은 앞의 글에서 거래처와 서류함을 B Tree 인덱스로 표현한 것으로, '거래처명'으로 B Tree 인덱스를 생성한 경우입니다.

 

 

 

Tree의  가장 위에는 트리 탐색을 시작하는 루트(Root, 뿌리) 노드가 위치합니다. 루트 노드는 하나의 인덱스에 오직 하나만 존재합니다. 루트 아래에는 브런치(Branch, 가지, 또는 미들(Middle)) 노드가 위치합니다. 브런치 노드는 루트에서 가장 아래에 있는 리프(Leaf) 노드를 찾아가는 중간 노드로서, 여러 층이 존재할 수 있습니다.(그림에서는 편의상 한 층만 표현했습니다.) 마지막으로 Tree의 가장 아래에는 리프(Leaf, 잎사귀) 노드가 존재합니다. 리프에는 실제 데이터가 존재하거나 실제 데이터가 위치한 주소 값을 저장합니다. 그리고 리프 노드를 보면 왼쪽부터 알파벳 순서대로 데이터가 입력되어 있으며 근접한 리프 노드끼리는 서로 연결되어 있습니다.

그림을 전체적으로 보면, 나무 뿌리가 가장 위에 있고, 나무 잎사귀가 가장 아래에 있습니다. 나무를 뒤집어서 그린 모양이라고 이해하시면 됩니다.

 

B Tree 인덱스(B+ Tree) 관련해서 중요한 내용을 정리해보면 아래와 같습니다.

  • '루트(뿌리), 브런치(가지, 미들), 리프(잎)' 세 개의 요소로 나누어진다.
  • 리프에는 실제 데이터 또는 실제 데이터가 존재하는 주소 값을 가지고 있다.
  • 리프 노드의 데이터는 정렬되어 있다.
  • 근접한 리프 노드는 서로 연결되어 있다.

 

오늘 살펴볼 내용은 여기까지입니다. B Tree와 같은 자료 구조 이야기가 나와 조금 어려울 수 있지만, 한 번쯤은 깊이 공부해볼 필요가 있는 내용입니다.

감사합니다.

 

 

'인덱스를 만들면 조회가 빨라지더라~'라는 것에 대해서는 이미 앞에서 살짝 테스트를 해봤습니다.
인덱스를 마스터하려면 인덱스의 종류, 구조등 다양한 내용을 공부해야 합니다. 이와 같은 복잡한 내용은 천천히 기회가 될 때 살펴보도록 하고, 우선은 '왜 .. 인덱스를 만들면 빨라지는거야?'에 대해서 개념을 살짝 잡아볼까 합니다.

1. 인덱스 왜 빨라?

회사에 거래처가 아래 그림 좌측과 같이 12개가 있다고 가정해 보겠습니다.
거래처별 거래 서류를 정리하기 위해, 아래 그림 오른쪽과 같은 네 칸으로 구분된 거래처 서류함을 사용하려고 합니다.
'나신입'님께 정리를 부탁했더니, 거래처 서류를 아무렇게나 세 개씩 나누어서 서랍에 넣습니다.
다시 말해, Airbnb 서류가 1번 칸에 있는지, 2번 칸에 있는지 알 수가 없습니다. 필요한 거래처 서류가 어디있는지는 서랍을 열어야만 알수 있는 상황이 된거죠.
필요한 거래처 서류를 운이 좋으면 한 번에 찾을 수 있지만, 운이 나쁘면 서랍 네개를 모두 열어야만 찾을 수 있습니다.


필요한 서류 하나를 찾을 때마다 서랍을 몇 번씩 열고 닫아야 합니다. 매우 비효율적이죠. 거기에 서랍을 열고 닫는 소리도 시끄럽고요.

도저히 참을 수 없어 '나경력'님께 재정리를 부탁해 봅니다.
'나경력'님은 거래처 서류를 모두 꺼내서 알파벳 순서대로 정렬 한 후에, 세 개씩 나누어서 다시 서류함 서랍에 넣습니다. 그리고 서류함 서랍마다 어떤 알파벳으로 시작하는 거래처 서류가 있는지 라벨링합니다.아래 그림과 같이 말이죠


위 그림과 같이 정리한 결과, 필요한 거래처 서류를 찾기 위해서는 서랍을 한 번만 열면 됩니다. Airbnb 거래처 서류는 당연히 1번 서랍에 있고, Tesla 서류는 당연히 4번 서랍에 있다는 것을 누구나 알 수 있습니다.(알파벳 순서만 알고 있다면요)
다시 정리해보면, 거래처 서류를 알파벳 순서로 정렬해 차례대로 서랍에 넣는다. 그리고 서랍에는 라벨링을 해 놓는다.
이것이 바로 인덱스의 원리입니다. "정렬된 구조로 만들어서 필요한 데이터를 빠르게 찾을 수 있게 만든다."
이 정도면, 인덱스 구조가 왜 빠른가에 대한 이해가 되지 않을까 싶습니다.

마지막으로 아래 그림을 살펴볼까요. 정렬된 서류와 서류함을 옆으로 돌리면 삼각형 구조의 모양이 되는 것을 알 수 있습니다. 이처럼 성능 관련해서 인덱스를 설명할 때는 삼각형 도형을 많이 사용한다는 것도 알아놓으시면 좋을거 같습니다.



오늘은 여기까지입니다. 감사합니다.

 

MySQL에서 테이블에 어떤 인덱스가 있고, 인덱스를 생성하거나 제거할 때 어떤 문법으로 처리하는지 살펴보겠습니다.

 

1. 인덱스 확인하기

인덱스는 테이블에 종속적입니다. 아래와 같은 명령어로 해당 테이블에 어떤 인덱스가 있는지 확인할 수 있습니다.

SHOW INDEX FROM MYTUNDB.T_ORD_BIG;

 

2. 인덱스 생성하기

MySQL에서는 인덱스를 생성하는 두 가지 문법이 있습니다. 하나는 CREATE INDEX이고, 또다른 하나는 ALTER TABLE을 통해 테이블에 인덱스를 추가하는 방법입니다. 아래와 같습니다. (아마도 이미 인덱스가 있다면 생성이 안되고 에러가 나겠죠.)

# CREATE INDEX
CREATE INDEX T_ORD_BIG_X1 ON MYTUNDB.T_ORD_BIG(ORD_DT);

# ALTER TABLE로 인덱스 추가
ALTER TABLE MYTUNDB.T_ORD_BIG ADD INDEX T_ORD_BIG_X1(ORD_DT);

 

3. 인덱스 제거하기

인덱스를 제거하는 문법 역시 두 가지입니다. 아래와 같습니다.

# DROP INDEX
DROP INDEX T_ORD_BIG_X1 ON MYTUNDB.T_ORD_BIG;

# ALTER TABLE로 인덱스 제거
ALTER TABLE MYTUNDB.T_ORD_BIG DROP INDEX T_ORD_BIG_X1;

 

 

인덱스 관련된 자세한 문법은 아래 레퍼런스를 참고해주세요.

 

MySQL :: MySQL 8.0 Reference Manual :: 13.1.15 CREATE INDEX Statement

13.1.15 CREATE INDEX Statement CREATE [UNIQUE | FULLTEXT | SPATIAL] INDEX index_name [index_type] ON tbl_name (key_part,...) [index_option] [algorithm_option | lock_option] ... key_part: {col_name [(length)] | (expr)} [ASC | DESC] index_option: { KEY_BLOC

dev.mysql.com

 

읽어주셔서 감사합니다.~!

 

 

 

테스트를 위한 DB 구성은 아래 글을 참고해 구성할 수 있습니다.

 

RDBMS의 SQL 성능을 좌우하는 요소 세 가지를 뽑아보자면 아래와 같습니다.

  • SQL문 자체
  • 인덱스(INDEX)
  • 통계 정보

 

이번 글에서는 세 가지 요소 중에 인덱스에 대해 살짝 맛을 보도록 하겠습니다. 인덱스의 개념에 대해서는 아마도 다음 글에 설명이 될거 같습니다.

 

1. INDEX를 만들어보자.

인덱스는 RDBMS에서 데이터를 빠르게 찾을 수 있도록 해주는 객체입니다. 보통은 테이블별로 인덱스를 여러 개 만들 수 있으며, 데이터를 접근하는 주요 패턴을 분석해 인덱스를 설계합니다.

데이터를 접근하는 주요 패턴을 분석한다는 것을 "아주 간단히" 이야기 하자면, SQL의 WHERE 절이나, 조인절에서 사용된 조건 컬럼을 분석하는 것입니다. ("아주 간단히"를 강조한 이유는, 실전에서 인덱스 설계는 그렇게 간단한 문제가 아니기 때문입니다.)

 

아래와 같은 SQL이 있다고 해보죠.

SELECT  COUNT(*) ORD_CNT
FROM    MYTUNDB.T_ORD_BIG T1
WHERE   T1.ORD_DT >= STR_TO_DATE('20170201','%Y%m%d')
AND     T1.ORD_DT < STR_TO_DATE('20170202','%Y%m%d');

위 SQL을 EXPLAIN ANALYZE를 통해 실제 실행계획을 추출해보면 아래와 같습니다. 아래 실행계획에서 가장 윗 줄에 actual time이 해당 SQL의 총 실행시간입니다. 535.476 ~ 535.478 밀리세컨드(0.53초)가 걸렸습니다.

-> Aggregate: count(0)  (actual time=535.476..535.478 rows=1 loops=1)
     -> Filter: ((mytundb.t1.ORD_DT >= <cache>(str_to_date('20170201','%Y%m%d'))) and (mytundb.t1.ORD_DT < <cache>(str_to_date('20170202','%Y%m%d'))))  (cost=31559.80 rows=33455) (actual time=535.463..535.463 rows=0 loops=1)
         -> Table scan on T1  (cost=31559.80 rows=301158) (actual time=1.767..418.311 rows=304700 loops=1)

 

 

위 SQL에서 WHERE절에 사용된 조건은 T_ORD_BIG의  ORD_DT 컬럼입니다. 그러므로 T_ORD_BIG에 ORD_DT에 인덱스를 만들어 성능 개선을 고려할 수 있습니다. 실제로 인덱스를 만들어보고 성능이 개선되는지 확인해봅니다.

아래 구문을 통해 T_ORD_BIG_X1이라는 인덱스를 생성합니다. T_ORD_BIG_X1은 MYTUNDB.T_ORD_BIG 테이블의 ORD_DT로 구성된 인덱스입니다.

CREATE INDEX T_ORD_BIG_X1 ON MYTUNDB.T_ORD_BIG(ORD_DT);

 

인덱스를 생성했다면, 다시 위의 SELECT SQL을 EXPLAIN ANALYZE로 실행해봅니다. 아래와 같은 실행계획이 나오는 것을 알 수 있습니다. 전체 실행 시간인, 가장 위의 actual time이 0.045 밀리세컨드(0.000045초) 밖에 안되는 것을 알 수 있습니다.

-> Aggregate: count(0)  (actual time=0.045..0.046 rows=1 loops=1)
     -> Filter: ((mytundb.t1.ORD_DT >= <cache>(str_to_date('20170201','%Y%m%d'))) and (mytundb.t1.ORD_DT < <cache>(str_to_date('20170202','%Y%m%d'))))  (cost=1.21 rows=1) (actual time=0.041..0.041 rows=0 loops=1)
         -> Index range scan on T1 using T_ORD_BIG_X1  (cost=1.21 rows=1) (actual time=0.039..0.039 rows=0 loops=1)

 

이처럼, WHERE 절의 조건에 인덱스를 잡는 것만으로, 아래와 같이 성능 개선이 되었습니다.

  • 개선전후 시간 : 0.53초 -> 0.000045초

 

지금 살펴본 내용으로 아래 정도의 개념을 알 수 있습니다. 

  • 인덱스를 만들면 조회 속도가 빨라진다.
  • WHERE 절의 조건 컬럼에 인덱스를 고려한다.

 

하지만, 이 개념과 지식만으로 인덱스를 만들어대기 시작하면 매우 위험해집니다. (시스템이 오히려 문제가 생길 수 있습니다.) 실전에서 튜닝을 하고, 인덱스 설계를 위해서는 더 많은 경험과 공부가 필요합니다.

 

오늘은 여기까지입니다. 감사합니다.

 

 

 

오늘은 MySQL에서 실제 실행계획을 확인하는 법을 살펴보겠습니다. 실제 성능 개선에 있어서, 개선전, 개선후 결과를 측정하기에 매우 유용한 방법이고, 성능 개선에 가장 도움이 되는 실행 계획을 보는 방법이 아닐까 싶습니다.

 

연습과 테스트를 위한 DB 구성이 필요하다면 아래 글을 참고해주세요.

 

1. EXPLAIN ANALYZE

MySQL에서 실제 실행된 SQL의 실행계획을 보기 위해서는 EXPLAIN ANALYZE를 사용합니다. 아래와 같이 EXPLAIN ANALYZE를 추가해서 SQL을 실행하면, SQL이 실제 실행이 되면서, 실제 사용한 실행계획을 확인할 수 있습니다. (이전글에서 살펴본 실행계획은 예상 실행계획입니다.)

EXPLAIN ANALYZE
SELECT  T2.CUS_ID, T2.CUS_NM ,T1.*
FROM    MYTUNDB.T_ORD_BIG T1
		INNER JOIN MYTUNDB.M_CUS T2 ON (T2.CUS_ID = T1.CUS_ID)
WHERE   T1.ORD_SEQ <= 10;

위 SQL을 실행하면, SQL의 결과대신에 아래와 같은 실행계획이 출력됩니다. 실행 계획만 출력되지만 실제 SQL은 실행된 것과 다름없습니다.

-> Nested loop inner join  (cost=14.03 rows=10) (actual time=0.871..0.898 rows=10 loops=1)
     -> Filter: (mytundb.t1.ORD_SEQ <= 10)  (cost=3.03 rows=10) (actual time=0.074..0.089 rows=10 loops=1)
         -> Index range scan on T1 using PRIMARY  (cost=3.03 rows=10) (actual time=0.070..0.080 rows=10 loops=1)
     -> Single-row index lookup on T2 using PRIMARY (CUS_ID=mytundb.t1.CUS_ID)  (cost=1.01 rows=1) (actual time=0.080..0.080 rows=1 loops=10)

아래의 MySQL 블로그를 통해 실제 실행계획에서 얻을 수 있는 정보를 알 수 있습니다.

https://dev.mysql.com/blog-archive/mysql-explain-analyze/

 

위 블로그에서 중요한 부분만 정리하면, 아래의 내용일 거 같습니다.

  • Actual time to get first row (in milliseconds)
  • Actual time to get all rows (in milliseconds)
  • Actual number of rows read
  • Actual number of loops

오늘 살펴볼 내용은 여기까지입니다.

 

추가로, 제가 직접 만든 무료 MySQL 튜닝툴을 소개합니다.~

https://sweetquant.tistory.com/457

 

[StartUP Tuning]MySQL,MariaDB SQL 튜닝툴

MySQL과 MariaDB의 SQL 튜닝을 보다 간편하게 진행할 수 있는, SUT(StartUP Tuning)를 소개합니다. SUT는 실제 MySQL 튜닝을 전문적으로 수행하기 위해 직접 만들 툴입니다. 또한 MySQL,MariaDB의 성능 개선 교육

sweetquant.tistory.com

 

 

 

 

* 이 글에서 사용하는 MySQL의 버젼은 8.0.22 입니다.

 

MySQL을 설치하면 Workbench라는 SQL 툴이 같이 설치됩니다. Workbench에서는 SQL의 실행계획을 그래픽으로 Visual하게 확인할 수도 있습니다.

 

1. Visual하게 실행 계획 보기

쿼리 창에, SQL을 입력한 후에 Workbench의 아래 메뉴를 실행합니다.

  - Workbench 상단 메뉴 중에 Query 선택 > Explain Current Statement 를 선택

  - 또는 Ctlr + Alx +  X 를 바로 누르셔도 됩니다.

SELECT  T2.CUS_ID, T2.CUS_NM ,T1.*
FROM    MYTUNDB.T_ORD_BIG T1
        INNER JOIN MYTUNDB.M_CUS T2 ON (T2.CUS_ID = T1.CUS_ID)
WHERE   T1.ORD_SEQ <= 10;

SQL을 입력한 후에 Explain Current Statement를 선택하면 아래와 같이 Visual하게 실행계획을 확인할 수 있습니다. Tree 형태나 표 형태로 볼때보다 훨씬 더 쿼리가 처리되는 내부적인 흐름을 보기에 유용합니다. 아래의 각 단계에 마우스를 가져다 대면 추가적인 정보도 확인할 수 있습니다. 보여지는 내용을 보면, 'EXPLAIN Format = JSON'의 내용을 요약해서 보여주는거 같습니다.

 

튜닝 관련해서는 오라클 쪽에서 많이 진행되다 보니, 튜너들에게는 위와 같은 형식 보다는 Tree 형태의 실행계획을 더 선호합니다. 어떤 방식으로 보든, 성능 개선이라는 결과를 얻을 수 있다면 각자 편한 형태로 보시는 걸 추천드립니다.

오늘은 여기까지입니다. 감사합니다.

 

 


SQL을 완벽히 배우는 방법 StartUP SQL!

StartUP SQL의 완전 무료 강의 노트를 오픈했습니다.(온라인 E-Book / ALL FREE ACCESS)

무료 영상 강의(자막 + 1.5배속 강추)


 


SQL 튜닝을 위해서는 실행 계획이라는 것을 볼 줄 알아야 합니다.
실행 계획은 RDBMS의 옵티마이져(쿼리최적화기)가 SQL을 DBMS 내부적으로 어떻게 처리하겠다고 만들어 놓은 작업 계획서입니다.
옵티마이져가 생성한 실행계획에 따라 SQL의 성능은 달라집니다. 옵티마이져는 SQL과 테이블의 통계, 인덱스 등을 참고해 최적의 실행계획을 만드려고 노력합니다.
우선은 MySQL에서 실행계획을 어떻게 볼 수 있는지 정도만 알고 넘어가면 될거 같습니다.

테스트를 위한 DB 구성은 아래 글을 참고해 구성할 수 있습니다.
- https://sweetquant.tistory.com/m/346

1. EXPLAIN

MySQL에서 제공하는 가장 고전적인 방법입니다. EXPLAIN 뒤에 쿼리를 적어 놓고 실행하면 됩니다.

EXPLAIN
SELECT  T2.CUS_ID, T2.CUS_NM ,T1.*
FROM    MYTUNDB.T_ORD_BIG T1
        INNER JOIN MYTUNDB.M_CUS T2 ON (T2.CUS_ID = T1.CUS_ID)
WHERE   T1.ORD_SEQ <= 10;

위와 같이 쿼리를 실행하면 아래와 같이 표형태로 실행계획이 출력됩니다. 오라클과 같은 환경에서 튜닝을 하던 분들께는 매우 생소한 스타일로 실행계획이 표현됩니다. 아래 실행계획을 이해하려면 각각의 항목을 이해해야 하는데, 이에 관련해서는 기회가 된다면 나중에 별도로 다루도록 하겠습니다.

2. EXPLAIN FORMAT = JSON

EXPLAIN 에 JSON 포맷을 지정할 수 있습니다. 아래와 같이 실행하면 JSON 형태의 실행계획이 나오면, EXPLAIN으로만 확인할 수 없는 다양한 정보도 같이 확인이 가능합니다. 다만, 정보가 너무 많고 길어서 비효율적입니다.

EXPLAIN FORMAT = JSON
SELECT  T2.CUS_ID, T2.CUS_NM ,T1.*
FROM    MYTUNDB.T_ORD_BIG T1
		INNER JOIN MYTUNDB.M_CUS T2 ON (T2.CUS_ID = T1.CUS_ID)
WHERE   T1.ORD_SEQ <= 10;

3. EXPLAIN FORMAT = TREE

MySQL 8부터는 Tree 형태의 실행계획을 제공합니다. 오라클을 주로 튜닝하셨던 분들께는 매우 유용한 기술입니다.

EXPLAIN FORMAT = TREE
SELECT  T2.CUS_ID, T2.CUS_NM ,T1.*
FROM    MYTUNDB.T_ORD_BIG T1
		INNER JOIN MYTUNDB.M_CUS T2 ON (T2.CUS_ID = T1.CUS_ID)
WHERE   T1.ORD_SEQ <= 10;

아래와 같은 Tree 형태의 실행계획이 나옵니다.

-> Nested loop inner join  (cost=14.03 rows=10)
     -> Filter: (mytundb.t1.ORD_SEQ <= 10)  (cost=3.03 rows=10)
         -> Index range scan on T1 using PRIMARY  (cost=3.03 rows=10)
     -> Single-row index lookup on T2 using PRIMARY (CUS_ID=mytundb.t1.CUS_ID)  (cost=1.01 rows=1)


Tip.
MySQL Workbench에서 Tree 형태나 Json 형태로 실행계획을 확인해보면, 실행계획의 내용이 모두 출력 안될 수도 있습니다. MySQL Workbench에서 출려 가능한 컬럼의 Bytes를 설정하기 때문입니다. 아래와 같은 과정으로 컬럼(Field)에서 보여줄 수 있는 길이의 제한을 풀어줘야 합니다.
- Workbench 상단 메뉴 중 Edit -> Preferences... 선택 -> 좌측에서 SQL Editior > SQL Execution 선택 >
- Max. Field alue Lengh to Display (in Bytes): 를 9999999 로 설정

MySQL 튜닝에 대해서 적어볼까 합니다.

튜닝을 위해서는 MySQL DBMS 설치와 데이터베이스를 구성해야 합니다.

 

 

1. DBMS 설치

MySQL 튜닝 관련된 글에서는 MySQL 8.0.22 윈도우즈 버젼을 사용합니다. 설치 파일은 아래 경로에서 다운로드 할 수 있습니다.

 

https://dev.mysql.com/downloads/mysql/

 

MySQL :: Download MySQL Community Server

Select Operating System: Select Operating System… Microsoft Windows Ubuntu Linux Debian Linux SUSE Linux Enterprise Server Red Hat Enterprise Linux / Oracle Linux Fedora Linux - Generic Oracle Solaris macOS Source Code Select OS Version: All Windows (x86

dev.mysql.com

현재(22년 10월) 최신 버젼은 8.0.31 이네요. 이 버젼을 다운로드해서 설치해도 상관 없을거 같지만, 버젼을 정확히 맟주고 싶다면, 아래 사이트에서 8.0.22 버젼을 찾아서 받으시면 됩니다.

https://downloads.mysql.com/archives/community/

 

MySQL :: Download MySQL Community Server (Archived Versions)

Please note that these are old versions. New releases will have recent bug fixes and features! To download the latest release of MySQL Community Server, please visit MySQL Downloads. MySQL open source software is provided under the GPL License.

downloads.mysql.com

 

MySQL 8 설치 과정은 대부분 Next 버튼을 누르면 될만큼 간단합니다. 그래도 궁금하다면 아래 글을 참고해주세요.

https://sweetquant.tistory.com/230

 

2.2 MySQL 8 설치하기

여기서는 Windows 기준으로 MySQL 8을 다운로드하고 설치하는 과정을 설명한다. (MySQL은 5.7 버전에서 5.8 버전으로 올라가면서 MySQL 5.8이 아니라 MySQL 8로 부르고 있다.) MySQL 8이 이미 설치되어 있다면,

sweetquant.tistory.com

 

2. 데이터베이스 생성하기

설치한 MySQL에 접속해서 데이터베이스를 생성합니다. 여기서는 아래와 같이 MYTUNDB 라는 이름으로 데이터베이스를 생성합니다.

CREATE DATABASE MYTUNDB;

 

데이터베이스를 생성한 후에는, 테이블 생성과 데이터 입력이 필요합니다. 아래 첨부파일을 다운로드해서 쿼리 툴에서 실행하시면 됩니다.

MySQL튜닝BOOSTER_DB구성.sql
1.04MB

위 파일을 다운로드해서 실행하면 아래와 같은 테이블들이 만들어집니다.

  - M_ITM: 아이템(상품) 마스터

  - M_CUS: 고객

  - T_ORD: 주문

  - T_ORD_DET: 주문 상세

  - T_ORD_BIG: 주문 (튜닝 연습에 적합한 Big 테이블)

 

위 테이블들은 저의 전작인 SQL BOOSTER 에서 사용하는 테이블입니다. 데이터 역시 동일합니다. 다만, SQL BOOSTER는 오라클용이었고, 현재 올린 스크립트는 MySQL용입니다.

 

MySQL 튜닝 연습을 위한 사전 준비는 여기까지입니다.

 

주식 종목 정보는 다양한 사이트에서 다양한 방법(크롤링, API, 다운로드)으로 가져올 수 있습니다.

오늘 소개하는 방법은 TwelveData에서 주식 종목을 가져오는 방법입니다.

https://twelvedata.com/

 

Twelve Data | Stock, Forex, and Crypto Market Data APIs

Try Twelve Data financial APIs for free and get instant access to stock, forex, cryptocurrency, and fundamental market data. Real time, historical, and EOD data.

twelvedata.com

 

 

TwelveData에서는 다양한 국가(미국, 한국, 중국, 일본, 유럽 곳곳까지)의 종목 정보를 가져올 수 있어서 매우 좋은거 같습니다. 그리고, 파이썬 코딩도 비교적 간단합니다. (저만 알고 싶은 곳이지만, 과감히 올려봅니다.) 또한 TwelveData에서는 종목 정보 외에도 주가 정보를 가져올 수 있는데, 무료 정책으로 하루에 800번의 API호출이 가능합니다. 현재 저는 무료 정책을 사용중이며, 주요 종목의 주가를 일별로 추적하기에는 800번의 API 호출이면 충분한거 같습니다.(단, 1분에 8번 제한이 있습니다.)

 

종목 정보를 가져오는 파이썬 소스는 아래와 같습니다. 참고하셔서 각자 환경에 맞게 개발하시면 될거 같습니다.

감사합니다.

import requests
import pandas as pd

def GetStockListFromTwelveData(_country, _type):
    url = "https://api.twelvedata.com/stocks"
    response = requests.get(url)
    js_txt = response.json()
    df = pd.DataFrame(js_txt['data'])  # dict에서 js_txt 키에 해당하는 값만 가져오기
    # print(df.country.unique()) 국가 값 종류 확인
    # print(df.type.unique()) Type 값 종류 확인
    # print(df.columns) # 컬럼 정보 확인
    df2 = df[(df.country == _country) & (df.type == _type)]  # 특정조건 값 가져오기

    return df2

# Contry
# ['Germany' 'China' 'South Korea' 'Hong Kong' 'Malaysia' 'Taiwan' 'India'
# 'United Kingdom' 'Italy' 'Mexico' 'Saudi Arabia' 'Poland' 'Japan''Sweden'
# 'Philippines' 'Thailand' 'Netherlands' 'Denmark' 'Spain''Hungary' 'South Africa'
# 'Brazil' 'Ireland' 'Canada' 'United States''Greece' 'Romania' 'Indonesia' 'Finland'
# 'Switzerland''United Arab Emirates' 'Kuwait' 'Argentina' 'Chile' 'Colombia'
# 'New Zealand' 'Belgium' 'Israel' 'Qatar' 'Russia' 'Botswana' ''
# 'Turkey''Portugal' 'Lithuania' 'Czech Republic' 'Estonia' 'Iceland' 'Latvia''Egypt']

# Type
# ['Common Stock' 'American Depositary Receipt' 'REIT' 'Unit'
#  'Global Depositary Receipt' 'Depositary Receipt' 'Preferred Stock'
#  'Limited Partnership' 'Structured Product' 'Right' 'Warrant' 'Trust'
#  'ETF' 'Mutual Fund']


pd.set_option('display.max_columns',None) # df 출력시 모든 컬럼 출력하도록 처리
pd.set_option('display.width',1000) # df 출력시, 한 로우가 따로 출력되지 않도록 충분한 길이 설정

df = GetStockListFromTwelveData('United States','Common Stock')
# Columns:
print(df)

 

 

 

알아놓으면 좋을, MySQL의 프로시저 소스 확인 하는 법입니다.

사용하는 툴에 따라서, 오브젝트 탐색기를 사용하시면 아주 쉽게 확인할 수 있겠지만,

명령어나 INFORMATION_SCHEMA의 오브젝트를 통해서도 확인할 수 있습니다.

1. SHOW 명령어로 확인하기.

  - SHOW 명령어 뒤에 CREATE PROCEDURE라고 명령해주셔야 합니다.

SHOW CREATE PROCEDURE [프로시저명];

2. INFORMATION_SCHEMA의 오브젝트를 조회해 확인하는 방법입니다.

  - 아래와 같이 ROUTINES을 조회하시면 됩니다.

SELECT T1.ROUTINE_DEFINITION
FROM   INFORMATION_SCHEMA.ROUTINES T1
WHERE  T1.ROUTINE_SCHEMA = [스키마명]
AND    T1.ROUTINE_NAME = [프로시저명];

 

이상입니다.

 


SQL을 완벽히 배우는 방법 StartUP SQL!

StartUP SQL의 완전 무료 강의 노트를 오픈했습니다.(온라인 E-Book / ALL FREE ACCESS)

무료 영상 강의(자막 + 1.5배속 강추)


 

 

추가로, 제가 직접 만든 무료 MySQL 튜닝툴을 소개합니다.~

https://sweetquant.tistory.com/457

 

[StartUP Tuning]MySQL,MariaDB SQL 튜닝툴

MySQL과 MariaDB의 SQL 튜닝을 보다 간편하게 진행할 수 있는, SUT(StartUP Tuning)를 소개합니다. SUT는 실제 MySQL 튜닝을 전문적으로 수행하기 위해 직접 만들 툴입니다. 또한 MySQL,MariaDB의 성능 개선 교육

sweetquant.tistory.com

 

정말 많은 사람들에게 힘든 주식 시장 같습니다.
이럴때일수록 공부를 열심히 하자라는 생각으로 다양한 책들을 살펴보고 있습니다.
지금까지 개인적으로 중장기 보유의 '투자' 관점에서 주식을 많이 접근했었는데요, 요즘에는 '매매'(트레이딩) 관점으로 공부를 좀 해보고 있습니다.

특정 종목 Z를 단순 보유한 경우와 추세추종으로 트레이딩한 경우의 수익을 비교해 봤습니다.(종목을 밝히지 않는 이유는, 속상하신 분도 있을 수 있으니 T.T)

이 글의 차트 보는 법에 대해 먼저, 정리드리면
- 검은색 점선: 해당 종목을 단순 보유할 경우 수익 변화입니다.(최초 천원어치 만큼 매수했다고 가정합니다.)
- 보라색 선: 트레이딩에 따른 수익 변화(최초 천원으로 계속 매매한 경우 수익 변화입니다.)
- 빨간 세로선: 트레이딩시 매수(Buy) 신호입니다.
- 파란 세로선: 트레이딩시 매도(Sell) 신호입니다.
- 여기서, 트레이딩은 제가 개인적으로 만든 추세추종 규칙입니다.

1. 2019~2019.12.31
- 단순 보유: 58% VS. 트레이딩 수익률: 7%
- 이 결과를 보면, 트레이딩 로직이 한참 문제가 있거나, 역시 단순 보유가 답일 수 있습니다.

2. 2019~202012.31
- 단순 보유: 220% VS. 트레이딩 수익률: 91%
- 역시나, 트레이딩 로직이 한참 문제가 있거나, 역시 단순 보유가 답일 수 있습니다.

3. 2019 ~ 2022.9.30(현재)
- 2019 ~ 2021.12.30은 생략했습니다.
- 단순 보유: 63% VS. 트레이딩 수익률: 91%
- 트레이딩 로직이 추세추종이기 때문에, 추세가 꺽인 시점에서는 더 이상 매수 신호가 나오지 않습니다.


지금까지의 차트를 살펴보면, 어떤 기간에는 단순 보유가, 그리고 지금과 같은 하락장에는 규칙적인 추체추종이 오히려 수익을 보존해주는 것을 알 수 있습니다. (물론, 위 차트는 특정 종목 하나이므로, 모든 종목에 해당한다 할 수는 없겠죠.)

위 결과만 보면, 단순 보유하다가 하락기 시작에 판다면 가장 좋은 성과를 얻을 수 있습니다. 하지만 그 시점에 과연 팔 수 있었을지는 아무도 모릅니다. 보통 내려가는 종목 보면, '휙 내리고 > 희망 고문 > 휙 내리고 > 희망 고문'의 반복이기 때문에 매도가 쉽게 나오지 못합니다.

혹시라도, 단순 보유 전략을 2021년부터 시작했다면 어떻게 될까요?
4. 2021 ~ 2022.9.30(현재)
- 단순 보유: -33% VS. 트레이딩 수익률: 5%
- 트레이딩의 수익이 좋지는 못합니다. 하지만, 2021부터 지금까지 보유했다면, 정말 힘든 상황이죠.


특정 시점과, 특정 종목 하나로 일반화하기는 어렵지만,
규칙적인 트레이딩이 대박은 아니더라도 중박은 유지할 수 있으며, 수익을 유지할 수 있는 방법인가라고 생각이 들기도 합니다. 물론 종목 선정과 기본적인 매매 규칙을 잘 만들고 지켜야만 이 마저도 수익을 얻을 수 있겠죠.

어쨋든! 트레이딩을 위해서는 더더욱 파이썬과 SQL 능력, 데이터를 분석하는 능력을 갈고 닦을 필요가 있다는 생각이 듭니다. 이상입니다.^^

안녕하세요. 오늘은 SK하이닉스를 좀 들여다 봤습니다.

결론부터 말씀드리면, 투자가 쉬운 종목은 아닌거 같습니다.

하지만, 제 개인적인 생각이니, 너무 심각하게 생각하지 말아주시기 부탁드리며,

참고하는 정도로 살펴주시면 감사하겠습니다.

설명에 앞서, 절대 어떤 종목을 추천하기 위한 글이 아니며, 본 글로 인해 투자한 종목의 손실에 대해서는 절대 누구도 책임지지 않는다는 것을 기억해주시기 바랍니다.

데이터의 정확성 역시 일차적으로는 검토했지만, 데이터 수집 시점이나 과정에서 발생한 실수로 부정확할수도 있으니 양해바랍니다.

아무래도 SK하이닉스의 주가는 D램 수출과 관련이 클 것으로 생각됩니다. 그래서 D램 수출입 정보를 먼저 모두 받아서 데이터화했습니다. 데이터화 과정은 이전 글을 참고해주세요.

https://blog.naver.com/ryu1hwan/222493425425

 

메모리 품목 수출입 데이터 DB화하기

오늘은 분석에 필요한, 또는 SQL 연습에 필요한 새로운 데이터를 추가해보겠습니다. 바로, 수출입 통계 ...

blog.naver.com

 

메모리 품목 수출 데이터와, SK 하이닉스의 월별 주가(월별 시작가격을 사용합니다.)를 섞어서 차트로 그려보면 아래와 같습니다.

그림을 살펴보면, 메모리 수출 금액과 SK하이닉스의 주가 움직임이 거의 유사해 보입니다. 구간별로 나누어서 정리해보면 아래와 같습니다.

2008년 11월부터 2017년 11월까지는 메모리(디램) 수출의 흐름과 SK하이닉스의 주가 움직임이 거의 유사한 것을 알 수 있습니다. 하지만, 2017년 12월부터는 메모리 수출이 최대치를 찍었지만, 주가는 그만큼 오르지 못했습니다. 아마도. 미중무역전쟁 때문 아닐까 싶습니다. 수출 역시, 미중무역전쟁으로 서둘러 메모리 수출이 된건 아닐까 하는 개인적으로 생각을 해봅니다.

2018년 11월부터 2020년 9월까지는 수출과 반대로 주가는 오르기도 했습니다. 일본과 우리나라 사이에 반도체 소재 관련 이슈때문이었나 생각도 듭니다.

결론적으로... 과거에는 메모리 수출과 SK하이닉스는 거의 비례해서 움직였지만, 2017년부터는 그렇지 못한거 같습니다. 다시 메모리 수출과 SK하이닉스 주가가 비례해서 움직일지는 더 지켜봐야 알 수 있을거 같습니다.

그러므로, 메모리 수출에 따라, SK하이닉스 투자를 결정하기에는 좀 어려워보입니다.

추가로, SK하이닉스 보유기간에 따른 수익률을 분석해봤습니다. 원리나 로직은 이전 글을 참고해주세요.

https://blog.naver.com/ryu1hwan/222723920974

 

LG생활건강을 6개월 보유하면?

오늘은 LG생활건강을 6개월 보유할때의 수익률을 분석해보려고 합니다. 설명에 앞서, 절대 어떤 종목을 ...

blog.naver.com

 

SK하이닉스를 6개월 보유한 경우 월별 수익률 차트입니다. 수익률이 좋은 경우도 있지만, 수익률이 좋지 못한 경우도 제법 있습니다.

보유 기간을 늘려서 분석해봤습니다. 아래는 12개월 보유한 경우입니다. 6개월 보유한 경우보다는, 12개월 보유한 경우가 수익률이 좀 더 안정적인 것으로 보입니다. 하지만, 지금 매수 하기에는 여전히 불안한 시점으로 보입니다.

이처럼 SK하이닉스를 분석해봤지만, 단기간 가격이 많이 빠지긴 했지만, 매수하기 좋은 시점은 아닌거 같습니다. 다시 말씀드리지만, 개인적인 생각이니, 각자 투자에 참고하시는 정도로만 읽어주시기 바랍니다. 이상입니다.

이처럼, 데이터를 분석하는 과정을 공부해보고 싶으신 분은 아래의 '평생 필요한 데이터 분석'의 교육 과정을 추천합니다. 교육을 통해 SQL을 배운다면, 위 내용을 좀 더 보강할 수도 있고, 자신만의 스타일로 분석을 할 수 있습니다. SQL을 완전히 자신의 것으로 만들 수 있는 교육이니 관심 가져보시기 바랍니다. 감사합니다.~!

 

https://cafe.naver.com/dbian/5259

 

「평생 필요한 데이터 분석(MySQL 과정)」 수강자 모집

MySQL 사용자를 위한 SQL 교육 과정을 모집합니다. 2021년 4월에 첫 강의를 성공리에 잘 마친 이후, 강사님의 프로젝트 일정이 너무 바쁜 탓에, 그리고 코로나 탓에 ...

cafe.naver.com

 

오늘은 LG생활건강을 6개월 보유할때의 수익률을 분석해보려고 합니다.

설명에 앞서, 절대 어떤 종목을 추천하기 위한 글이 아니며, 본 글로 인해 투자한 종목의 손실에 대해서는 절대 누구도 책임지지 않는다는 것을 기억해주시기 바랍니다.

데이터의 정확성 역시 일차적으로는 검토했지만, 데이터 수집 시점이나 과정에서 발생한 실수로 부정확할수도 있으니 양해바랍니다.

방법은 아래와 같습니다.

 1. 2010년1월의 시가(시작가격)로 매수, 6개월 보유 후, 2010년 7월의 시가로 매도

 2. 2010년 2월의 시가로 매수, 6개월 보유, 2010년 8월 시가로 매도

 3. LG생활건강의 모든 월별 주가에 대해 위와 같이 처리

위와 같은 로직으로 각 월별로 6개월 보유후 매도했을 때의 등락률(수익률)을 구해서 차트를 그려봅니다. 위에서 설명한 결과를 만다는 SQL은 아래와 같습니다.(아래는 오라클 기준의 SQL입니다. MySQL도 크게 다르지 않습니다.)

SELECT  T1.STK_CD ,T1.STK_NM
        ,REGEXP_REPLACE(T_SELL.YM,'(.{4})(.{2})','\1-\2') 매도월 
        ,T_SELL.O_PRC 매도가격
        ,ROUND((T_SELL.O_PRC - T_BUY.O_PRC) / T_BUY.O_PRC * 100,2) 등락률
        ,REGEXP_REPLACE(T_BUY.YM,'(.{4})(.{2})','\1-\2') 매수월
        ,T_BUY.O_PRC BUY_PRC -- 시가 사용
FROM    ORA_STK_USER.M_STK T1
        ,ORA_STK_USER.FT_STK_YM T_SELL
        ,ORA_STK_USER.FT_STK_YM T_BUY
WHERE   T1.STK_CD = T_SELL.STK_CD
AND     T1.STK_NM = 'LG생활건강'
AND     T_BUY.STK_CD = T1.STK_CD
AND     T_BUY.YM = TO_CHAR(ADD_MONTHS(TO_DATE(T_SELL.YM||'01','YYYYMMDD'),-6),'YYYYMM')
ORDER BY T_SELL.YM ASC
 

위 SQL을 통해 아래와 같은 결과를 얻을 수 있습니다. 첫 번째 결과 데이터를 보면, 2010년에 1월에 매수해서 2010년 7월에 매도했을 경우, 등락률(수익률)이 20.27%입니다. 두 번째 결과 데이터는 2010년 2월에 매수해서 2010년 8월에 매도한 경우로 등락률이 30.68%입니다.

STK_CD	STK_NM	    매도월	매도가	등락률	매수월	BUY_PRC
------- ----------- ------- ------- ------- ------- --------
... 생략 ...
051900	LG생활건강	2010-07	350000	20.27	2010-01	291000
051900	LG생활건강	2010-08	377000	30.68	2010-02	288500
051900	LG생활건강	2010-09	400000	44.4	2010-03	277000
051900	LG생활건강	2010-10	417500	39.17	2010-04	300000
051900	LG생활건강	2010-11	380500	24.75	2010-05	305000
051900	LG생활건강	2010-12	387000	21.89	2010-06	317500
051900	LG생활건강	2011-01	393000	12.29	2010-07	350000
... 생략 ...

 

위 결과를 통해서는, 딱히 투자를 어떻게 해야 할까 방향을 잡기도 쉽지 않습니다. 데이터 분석과 활용을 위해서는 테이블 형태의 데이터를 차트화할 필요가 있습니다. 위 결과를 엑셀로 내려받아 차트를 만들어 보면 아래와 같습니다.

그림에서, 녹색바는 6개월 보유후 매도했을 때의 등락률(수익률)입니다. 파란색 선은 해당월의 시작가격입니다.

그림을 보시면, LG생건의 경우, 6개월 보유했을때, 손실나는 경우보다 수익나는 경우가 전반적으로 더 많습니다.

또한, 과거 최대 손실이 -30과 -50 사이입니다. 그러므로 6개월전 대비 -30~-50 사이로 주가가 내려간 시점이라면 매수할만 하지 않을까 생각해 볼 수 있습니다. (더이상 손실 나는 케이스는 잘 없었으니까요.)

그래서, 저역시, 올해 22년 3월 경에 자신있게 매수했더랍니다. 하지만. 역시!!! 보기 좋게 더 미끄러지고 말았습니다. (그림을 보면서 더 생각해보면, 더 기다렸다가, 6개워 등락률이 차차 올라가 다시 0보다 커지기 시작하는 시점에 매수하면 좀더 안정적이지 않았을까 싶습니다.)

과거의 이벤트와 위의 차트를 비교해서 그려보면 아래와 같습니다.

엑셀은 아래에 첨부합니다.

LG생활건강_6개월보유수익률.xlsx
0.03MB

 

항상 그런 거은 아니지만, LG생건의 경우 중국에 따라 주가 움직임의 영향이 큰 것으로 보입니다. 많은 분들도 기본적으로 그렇게 알고 있으리라 생각합니다.

현재, LG 생건에게 호재는 실외마스크 해제와 가격이 너무 많이 빠졌다, 그리고 해외 여행이 시작되었다. 정도입니다.

반면에, 악재를 생각해보면, 금리인상, 러시아 전쟁, 인플레이션, 양적완화 축소, 상하이 봉쇄, 중국 아시안게임 연기가 있습니다. 호재를 능가할만한 악재들이지 않나 싶습니다.

주식 투자라는게 참 어렵습니다. 지난 통계나 가격을 살펴보면, 분명히 사야할 시점 같지만, 다양한 경제 현황을 살펴보면 사면 안되는 시점이기도 합니다. 결국 어찌해야 할지는 본인이 장고끝에 결정을 내려야 한다고 봅니다.

위와 같은 보유개월별, 수익률 변화는 결정을 내리는데 참고 정보로 사용할만하지 않을까 싶습니다.

이상입니다.

이처럼, 데이터를 분석하는 과정을 공부해보고 싶으신 분은 아래의 '평생 필요한 데이터 분석'의 교육 과정을 추천합니다. 교육을 통해 SQL을 배운다면, 위 내용을 좀 더 보강할 수도 있고, 자신만의 스타일로 분석을 할 수 있습니다. SQL을 완전히 자신의 것으로 만들 수 있는 교육이니 관심 가져보시기 바랍니다.

감사합니다.~!

https://cafe.naver.com/dbian/5259

 

"금리인상" 여기저기서 흘러나온지 제법되었습니다.

늦엊지만, 금리인상에는 어떤 종목에 투자해야 할지 고민을 해보려고 합니다.

설명에 앞서, 절대 어떤 종목을 추천하기 위한 글이 아니며, 본 글로 인해 투자한 종목의 손실에 대해서는 절대 누구도 책임지지 않는다는 것을 기억해주시기 바랍니다.

데이터의 정확성 역시 일차적으로는 검토했지만, 데이터 수집 시점이나 과정에서 발생한 실수로 부정확할수도 있으니 양해바랍니다.

여기서는 실제 금리인상의 추세와 주가의 추세를 연결짓지 않고, 구글트렌드를 활용해, '금리인상' 키워드에 대한 트레드와 주요 주가의 추세를 연결해서 분석해보려고 합니다. (구글 트렌드는 투자에서도 여러모로 쓸모가 있답니다.)

먼저 구글 트렌드에 접속해 '금리인상'에 대해 검색을 합니다. 최대한 오랜 기간의 데이터를 검색합니다.

2022년5월5일(어린이날에 글을 올리고 있군요.!) 기준으로 아래와 같이 결과가 나옵니다.

살펴보니, 2010년 중반과, 2015년 말에 금리인상에 대한 키워드가 위로 튄 것을 알 수 있습니다. 그리고, 현재, 2022년 역시, 강력하게 금리인상에 대한 관심도가 강력하게 치솟고 있습니다.

금리인상 관심도가 증가했다는 것은, 금리인상을 실제 했다는 뜻이겠죠. 위의 데이터를 엑셀로 다운받아 봅니다.

다운해보면, 가장 높은 관심도가 100이 되고, 나머지는 100에 비례한 비율로 수치가 만들어져 있습니다. 이제 엑셀로 다운받은 '금리인상' 관심도와 주가를 엮어서 분석해볼 차례입니다. 내부적으로는 모든 종목을 해볼 수도 있겠지만, 여기서는 아래 종목들만 추려서 분석해보려고 합니다.

  • 우리나라 대표 종목인 삼성전자
  • 우리나라 금융 대표 종목인 KB금융
  • 우리나라 빅테크 대표 종목인 네이버
  • 우리나라 지수 대표 ETF인 KODEX 200

위 종목들의 월별 주가를 아래 SQL로 다운 받을 수 있습니다.(제가 별도 관리하고 있는 월별주가 데이터입니다.)

쿼리에 대한 설명은 복잡하므로 생략합니다.^^ 주가를 실제 가격이 아닌 최대 가격에 비례한 비율로 데이터를 처리한다는 점만 주의하면 될거 같습니다.

WITH R01 AS(
SELECT  T1.YM
        ,MAX(CASE WHEN T2.STK_NM = 'KODEX 200' THEN T1.O_PRC END) PRC_KODEX
        ,MAX(CASE WHEN T2.STK_NM = '삼성전자' THEN T1.O_PRC END) PRC_SAM
        ,MAX(CASE WHEN T2.STK_NM = 'KB금융' THEN T1.O_PRC END) PRC_KB
        ,MAX(CASE WHEN T2.STK_NM = 'NAVER' THEN T1.O_PRC END) PRC_NAVER
        ,MAX(MAX(CASE WHEN T2.STK_NM = 'KODEX 200' THEN T1.O_PRC END)) OVER() PRC_KODEX_MAX
        ,MAX(MAX(CASE WHEN T2.STK_NM = '삼성전자' THEN T1.O_PRC END)) OVER() PRC_SAM_MAX
        ,MAX(MAX(CASE WHEN T2.STK_NM = 'KB금융' THEN T1.O_PRC END)) OVER() PRC_KB_MAX
        ,MAX(MAX(CASE WHEN T2.STK_NM = 'NAVER' THEN T1.O_PRC END)) OVER() PRC_NAVER_MAX
FROM    ORA_STK_USER.FT_STK_YM T1
        ,ORA_STK_USER.M_STK T2
WHERE   T1.STK_CD = T2.STK_CD
AND     T2.STK_NM IN ('KODEX 200','삼성전자','KB금융','NAVER')
GROUP BY T1.YM
ORDER BY T1.YM
)
SELECT  T1.YM
        ,NVL(ROUND(T1.PRC_KODEX/T1.PRC_KODEX_MAX * 100,1),0) KODEX
        ,NVL(ROUND(T1.PRC_SAM/T1.PRC_SAM_MAX * 100,1),0) SAM
        ,NVL(ROUND(T1.PRC_KB/T1.PRC_KB_MAX * 100,1),0) KB
        ,NVL(ROUND(T1.PRC_NAVER/T1.PRC_NAVER_MAX * 100,1),0) NAVER
FROM    R01 T1
WHERE   T1.YM >= '200401'
ORDER BY T1.YM;

 

 

위 결과와, 구글트렌드에서 다운받은 금리인하 트렌드 엑셀과 연결해서 차트를 그려보면 아래와 같습니다.

금리인상 트렌드는 굵은 파란색 선입니다. 2004년부터 2022년까지의 관심 트렌드와 주요 주가가 월별로 나타나 있습니다.

살펴보면, 금리인상 관심 트렌드가 튄 시점 이후로, 삼성전자, KB금융, KODEX 200(삼성전자 영향이 크겠죠.) 모두 상승 흐름인 것으로 보입니다. 반면에 우리나라 빅테크 기업인 NAVER는 주가가 흐르거나, 딱히 오르지 않는것도 알 수 있습니다.

이를 통해,

"금리인상과 금융주가 상관관계가 있다. 삼성전자와 같은 우량주도 괜찮다. NAVER와 같은 테크 기업은 좋지 않을 수 있다." 라고 생각할 수 있습니다. 하지만, 이와 같은 과거 데이터 분석이 항상 미래에도 똑같을 것이라고 장담할 수는 없습니다. 저희는 단지, 위 내용을 참고해 자신만의 투자 전략을 세워야 한다고 생각합니다.

앞으로 펼쳐질 미래에는 정반대로 갈 수도 있으니, 이에 대한 시나리오도 세우고 투자를 해야 한다고 생각합니다.

엑셀 파일은 아래에 첨부합니다.

금리인상_트렌드.xlsx
0.03MB

 

이처럼, 데이터를 분석하는 과정을 공부해보고 싶으신 분은 아래의 '평생 필요한 데이터 분석'의 교육 과정을 추천합니다. 교육을 통해 SQL을 배운다면, 위 내용을 좀 더 보강할 수도 있고, 자신만의 스타일로 분석을 할 수 있습니다. 좀 비싼 교육이긴 하지만, SQL을 완전히 자신의 것으로 만들 수 있는 교육이니 관심 가져보시기 바랍니다.

감사합니다.~!

 

https://cafe.naver.com/dbian/5259

 

「평생 필요한 데이터 분석(MySQL 과정)」 수강자 모집

MySQL 사용자를 위한 SQL 교육 과정을 모집합니다. 2021년 4월에 첫 강의를 성공리에 잘 마친 이후, 강사님의 프로젝트 일정이 너무 바쁜 탓에, 그리고 코로나 탓에 ...

cafe.naver.com

 

지난 글에서는 셀프조인에 대해 살펴봤습니다.

 

https://sweetquant.tistory.com/322

 

셀프 조인 이해하기

안녕하세요. 오늘은 셀프 조인(Self Join)에 대해 정말 간단하게 설명해볼까 합니다. 준비중인 다음 글을 위해 간단히 개념을 설명하려 합니다. ​ 이 글에 포함된 SQL은 오라클 기준으로 작성되었

sweetquant.tistory.com

 

셀프조인에 이어서, 카테시안 조인에 대해 살펴보도록 하겠습니다.

발생하지 않은 실적 데이터를 분석하기 위해서는 임의로 분석 차원(분석 대상 집합)을 만들어줄 필요가 있습니다. 이를 위해 필요한 것이 바로 카테시안 조인입니다.

카테시안 조인은 A 집합의 데이터 각각이 B 집합의 모든 데이터와 조인되는 것을 뜻합니다.

SQL BOOSTER에서는 아래 그림을 사용해 카테시안 조인을 설명합니다. CUS_GD 집합 두 건이 ITM_TP 집합 네 건과 모두 조인 처리되어, 3번과 같이 총 8건의 데이터가 만들어집니다.

SQL BOOSTER의 카테시안 조인 설명을 위한 그림

 

카테시안 조인 연습을 위해 아래와 같이 두 개의 테이블을 생성합니다. (앞에 글에서 만든 T_SALE_YM_01 테이블처럼 오라클에서 테이블을 생성합니다. T_SALE_YM_01과 연계해서 테스트를 할 예정입니다.)

CREATE TABLE M_ITEM_01
(
   ITEM_ID VARCHAR2(100) NOT NULL
   ,ITEM_NM VARCHAR2(100) NOT NULL
   ,ITEM_CTG_CD VARCHAR2(100) NOT NULL
   ,COLR_CD VARCHAR2(100) NULL
   ,SIZE_MM NUMBER(18,6) NULL
);
ALTER TABLE M_ITEM_01 ADD CONSTRAINT PK_M_ITEM_01 PRIMARY KEY(ITEM_ID);

CREATE TABLE M_YM_01
(
    YM VARCHAR2(6) NOT NULL
);
ALTER TABLE M_YM_01 ADD CONSTRAINT PK_M_YM_01 PRIMARY KEY(YM);

아이템 마스터를 저장하는 M_ITEM_01과 연월 값을 저장하는 M_YM_01 테이블을 생성했습니다. 만들어진 테이블에 데이터를 입력해야 합니다. M_ITEM_01에는 아래 스크립트를 사용해 데이터를 입력합니다.

INSERT INTO M_ITEM_01(ITEM_ID ,ITEM_NM ,ITEM_CTG_CD ,COLR_CD ,SIZE_MM) VALUES('M9B', 'Mobile9B' ,'MOBILE' ,'BLUE' ,158.4);
INSERT INTO M_ITEM_01(ITEM_ID ,ITEM_NM ,ITEM_CTG_CD ,COLR_CD ,SIZE_MM) VALUES('M9R', 'Mobile9R' ,'MOBILE' ,'RED' ,158.4);
INSERT INTO M_ITEM_01(ITEM_ID ,ITEM_NM ,ITEM_CTG_CD ,COLR_CD ,SIZE_MM) VALUES('W3B', 'Watch3B'  ,'SMARTWATCH' ,'BLUE' ,44);
INSERT INTO M_ITEM_01(ITEM_ID ,ITEM_NM ,ITEM_CTG_CD ,COLR_CD ,SIZE_MM) VALUES('G1B', 'Glass1B'  ,'SMARTGLASS' ,'BLUE' ,68);
COMMIT;

M_YM_01에는 아래와 같이 CONNECT BY를 사용해 2019년 1월부터 2021년 12월까지의 데이터를 입력합니다.

INSERT INTO M_YM_01(YM)
SELECT TO_CHAR(ADD_MONTHS(TO_DATE('20190101','YYYYMMDD'),ROWNUM-1),'YYYYMM') YM
FROM   DUAL T1
CONNECT BY TO_CHAR(ADD_MONTHS(TO_DATE('20190101','YYYYMMDD'),ROWNUM-1),'YYYYMM') <= '202112'
COMMIT;

데이터 입력을 완료했으면, 우선은 아래 SQL을 사용해 T_SALE_YM_01의 데이터를 2019년 12월부터 2020년 2월까지 조회해봅니다.

SELECT  TSAL.SALE_YM
        ,TSAL.ITEM_ID
        ,TSAL.SALE_QTY
FROM    T_SALE_YM_01 TSAL
WHERE   TSAL.SALE_YM BETWEEN '201912' AND '202002'
ORDER BY TSAL.SALE_YM ,TSAL.ITEM_ID;
[결과]
SALE_YM ITEM_ID     SALE_QTY 
------- ----------- --------- 
202001  G1B                18
202001  M9B               100
202001  M9R                45
202001  W3B                30
202002  G1B                15
202002  M9B                50
202002  M9R                25
202002  W3B                19

결과를 보면 2019년 12월 데이터가 존재하지 않습니다. T_SALE_YM_01에는 2020년 데이터만 존재하기 때문입니다. 판매가 없는 2019년 12월 데이터도 0으로 보여주고 싶다면, 아래와 같이 M_YM_01과의 아우터 조인을 고려해야 합니다.

SELECT  T1.YM
        ,TSAL.ITEM_ID
        ,TSAL.SALE_QTY
FROM    M_YM_01 T1
        ,T_SALE_YM_01 TSAL
WHERE   T1.YM BETWEEN '201912' AND '202002'
AND     TSAL.SALE_YM(+) = T1.YM
ORDER BY T1.YM ,TSAL.ITEM_ID;
[결과]
YM     ITEM_ID       SALE_QTY 
------ ------------- --------- 
201912 <null>           <null>
202001 G1B                  18
202001 M9B                 100
202001 M9R                  45
202001 W3B                  30
202002 G1B                  15
202002 M9B                  50
202002 M9R                  25
202002 W3B                  19

결과를 보면, 2019년 12월 데이터가 조회되긴 했지만 ITEM_ID와 SALE_QTY가 NULL 값으로 한 건만 조회가 되었습니다. ITEM_ID 값을 T_SALE_YM_01에서 가져와야 하는데, 2019년 12월에 데이터가 없기 때문에 조회되지 않습니다. 그러므로, M_YM_01과 M_ITEM_01을 카테시안 조인으로 연월, 아이템별 데이터 집합을 생성한 후에 아우터 조인을 처리해야 합니다. 연월, 아이템별 데이터 집합을 만드는 카테시안 조인 SQL을 살펴보면 아래와 같습니다.

SELECT  T1.YM
        ,T2.ITEM_ID
FROM    M_YM_01 T1
        ,M_ITEM_01 T2
WHERE   T1.YM BETWEEN '201912' AND '202002'
ORDER BY T1.YM ,T2.ITEM_ID;
[결과]
YM     ITEM_ID      
------ ------------- 
201912 G1B          
201912 M9B          
201912 M9R          
201912 W3B          
202001 G1B          
202001 M9B          
202001 M9R          
202001 W3B          
202002 G1B          
202002 M9B          
202002 M9R          
202002 W3B

결과를 보며, 2019년 12월도 아이템별 데이터가 만들어진 것을 알 수 있습니다. SQL을 살펴보면 두 테이블을 연결하는 조인 조건이 없습니다. 이처럼 카테시안 조인을 위해서는 두 테이블을 연결하는 조인 조건을 주지 않습니다. 일반적으로 ANSI 구문을 사용할 때는, 카테시안 조인 처리를 위해 아래와 같이 CROSS JOIN을 사용합니다.

SELECT  T1.YM
        ,T2.ITEM_ID
FROM    M_YM_01 T1
        CROSS JOIN M_ITEM_01 T2
WHERE   T1.YM BETWEEN '201912' AND '202002'
ORDER BY T1.YM ,T2.ITEM_ID;

마지막으로 2019년 12월부터 2020년 2월까지 연월별, 아이템별 판매 데이터를 조회하기 위해서는 아래와 같이 SQL을 사용하면 됩니다. 카테시안 조인으로 만들어낸 연월별, 아이템별 데이터 집합에 아우터 조인을 사용해 실적 데이터를 연결해주면 됩니다.

SELECT  T0.*
        ,NVL(T1.SALE_QTY,0) SALE_QTY
FROM    (
		SELECT  T1.YM
		        ,T2.ITEM_ID
		FROM    M_YM_01 T1
		        ,M_ITEM_01 T2
		WHERE   T1.YM BETWEEN '201912' AND '202002'
		ORDER BY T1.YM ,T2.ITEM_ID
		) T0
		,T_SALE_YM_01 T1
WHERE   T1.SALE_YM(+) = T0.YM
AND     T1.ITEM_ID(+) = T0.ITEM_ID
ORDER BY T0.YM ,T0.ITEM_ID;
[결과]
YM     ITEM_ID      SALE_QTY 
------ ------------ --------- 
201912 G1B                  0
201912 M9B                  0
201912 M9R                  0
201912 W3B                  0
202001 G1B                 18
202001 M9B                100
202001 M9R                 45
202001 W3B                 30
202002 G1B                 15
202002 M9B                 50
202002 M9R                 25
202002 W3B                 19

오늘은 여기까지입니다. 존재하지 않는 실적 데이터를 결과에 포함하기 위해서는 이처럼 카테시안 조인으로 분석 마스터가 될 수 있는 차원을 만들수 있어야 합니다.

위와 같이 데이터를 마음대로 분석해볼 수 있는 SQL을 공부하고 싶다면 아래 책을 참고해주세요~!

※ 책 소개: https://sweetquant.tistory.com/243

※ 책 미리보기: https://sweetquant.tistory.com/257

※ 완전판 E-Book

  ▶ 유페이퍼: https://www.upaper.net/ryu1hwan/1142997

  ▶ 알라딘: https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=273701425

  ▶ Yes24: http://www.yes24.com/Product/Goods/102264444?OzSrank=1

  ▶ 교보문고: https://digital.kyobobook.co.kr/digital/ebook/ebookDetail.ink?selectedLargeCategory=001&barcode=4801167630019&orderClick=LAG&Kc=

 

안녕하세요. 오늘은 셀프 조인(Self Join)에 대해 정말 간단하게 설명해볼까 합니다.

준비중인 다음 글을 위해 간단히 개념을 설명하려 합니다.

이 글에 포함된 SQL은 오라클 기준으로 작성되었습니다. 예제는 이전 글에서 만든 T_SALE_YM_01 테이블을 사용합니다. 연습을 위해서는 이전 글을 참고해 테이블과 데이터를 생성해주세요.

https://sweetquant.tistory.com/320

 

데이터 분석 - 오라클의 CORR 집계함수

오늘은 오라클의 집계 함수 중에 상관관계를 구하는 CORR 함수에 대해 설명해보려고 합니다. CORR 함수 하나만으로도 자신의 시스템에서 다양한 인사이트를 찾아낼 수 있습니다. 예를 들어, 비슷

sweetquant.tistory.com

 

 

자기 자신과 조인하는 것을 셀프 조인(Self Join)이라고 합니다. INNER JOIN이나 OUTER JOIN처럼 조인 문법이나 방법으로 존재하는 것이 아니라, 조인 대상에 따라 셀프 조인을 특정해서 부릅니다. 다시 말해, 'TAB1 JOIN TAB1'과 같이 TAB1끼리 조인하는 것을 셀프 조인이라고 합니다.

앞에서 만든 T_SALE_YM_01 테이블을 사용한 예제를 살펴보겠습니다. 아래 예제는 T_SALE_YM_01을 조회하면서, 3개월전 판매 수량을 가져오고, 현재와 3개월전 판매 수량을 비교해 판매가 얼만큼 증감했는지 조회하는 SQL입니다.

SELECT  T1.ITEM_ID
        ,T1.SALE_YM ,T1.SALE_QTY
        ,T2.SALE_YM BF3_YM ,T2.SALE_QTY BF3_QTY
        ,ROUND(T1.SALE_QTY / T2.SALE_QTY * 100,2) BF3_NW_SALE_RT
FROM    T_SALE_YM_01 T1
        LEFT OUTER JOIN T_SALE_YM_01 T2
           ON (T2.ITEM_ID = T1.ITEM_ID
               AND T2.SALE_YM = TO_CHAR(ADD_MONTHS(TO_DATE(T1.SALE_YM||'01','YYYYMMDD'),-3),'YYYYMM'))
WHERE   T1.ITEM_ID = 'M9B'
ORDER BY T1.SALE_YM;

위 SQL을 살펴보면, FROM 절의 LEFT OUTER JOIN을 중심으로 왼쪽과 오른쪽에 모두 T_SALE_YM_01 테이블을 사용하고 있습니다. 그러므로 셀프 조인이 됩니다. 위 SQL은 ANSI 기준 문법으로 작성했는데요, 아래와 같이 (+)만 추가하면 손쉽게 오라클 기준의 아우터 조인으로 변경할 수 있습니다.

SELECT  T1.ITEM_ID
        ,T1.SALE_YM ,T1.SALE_QTY
        ,T2.SALE_YM BF3_YM ,T2.SALE_QTY BF3_QTY
        ,ROUND(T1.SALE_QTY / T2.SALE_QTY * 100,2) BF3_NW_SALE_RT
FROM    T_SALE_YM_01 T1
        ,T_SALE_YM_01 T2
WHERE   T1.ITEM_ID = 'M9B'
AND     T2.ITEM_ID(+) = T1.ITEM_ID
AND     T2.SALE_YM(+) = TO_CHAR(ADD_MONTHS(TO_DATE(T1.SALE_YM||'01','YYYYMMDD'),-3),'YYYYMM')
ORDER BY T1.SALE_YM;

 

위 SQL의 결과를 살펴보면 아래와 같습니다.

T_SALE_YM_01(T1)                   T_SALE_YM_01(T2)      T1과 T2의 값을 계산
-----------------------------      ----------------      --------------
ITEM_ID     SALE_YM SALE_QTY       BF3_YM BF3_QTY        BF3_NW_SALE_RT
----------- ------- ---------      ------ ---------      -------------- 
M9B         202001        100      <null>    <null>              <null>
M9B         202002         50      <null>    <null>              <null>
M9B         202003         90      <null>    <null>              <null>
M9B         202004         10      202001       100                  10
M9B         202005         90      202002        50                 180
M9B         202006        100      202003        90              111.11
M9B         202007         90      202004        10                 900
M9B         202008         50      202005        90               55.56
M9B         202009         10      202006       100                  10
M9B         202010         20      202007        90               22.22
M9B         202011         30      202008        50                  60
M9B         202012         20      202009        10                 200

T1의 값들에 3개월 전 판매수량(T2)이 연결된 것을 알 수 있습니다. 이때, 2020년 1월의 3개월전인 2019년 데이터는 T_SALE_YM에 존재하지 않으므로 NULL로서 결과가 나온 것을 확인할 수 있습니다.

이처럼 자기 자신과 이루어지는 조인을 흔히 셀프 조인이라고 부릅니다.

위 SQL은 아래와 같이 LEAD 분석함수로도 해결 할 수 있습니다.

SELECT  T1.ITEM_ID
        ,T1.SALE_YM ,T1.SALE_QTY
        ,LEAD(T1.SALE_YM,3) OVER(ORDER BY T1.SALE_YM DESC) BF3_YM
        ,LEAD(T1.SALE_QTY,3) OVER(ORDER BY T1.SALE_YM DESC) BF3_QTY
        ,ROUND(T1.SALE_QTY / LEAD(T1.SALE_QTY,3) OVER(ORDER BY T1.SALE_YM DESC) * 100,2) BF3_NW_SALE_RT
FROM    T_SALE_YM_01 T1
WHERE   T1.ITEM_ID = 'M9B'
ORDER BY T1.SALE_YM;

위 SQL의 결과 역시, 셀프 조인을 사용한 SQL과 완전히 같습니다. 하지만, 분석함수의 치명적인 문제는 조회 결과만이 분석대상으로 포함되므로, 아래와 같이 특정 월 데이터를 조회하면 원하는 결과를 얻을 수 없습니다.

SELECT  T1.ITEM_ID
        ,T1.SALE_YM ,T1.SALE_QTY
        ,LEAD(T1.SALE_YM,3) OVER(ORDER BY T1.SALE_YM DESC) BF3_YM
        ,LEAD(T1.SALE_QTY,3) OVER(ORDER BY T1.SALE_YM DESC) BF3_QTY
        ,ROUND(T1.SALE_QTY / LEAD(T1.SALE_QTY,3) OVER(ORDER BY T1.SALE_YM DESC) * 100,2) BF3_NW_SALE_RT
FROM    T_SALE_YM_01 T1
WHERE   T1.ITEM_ID = 'M9B'
AND     T1.SALE_YM = '202012'
ORDER BY T1.SALE_YM;

ITEM_ID     SALE_YM SALE_QTY  BF3_YM BF3_QTY   BF3_NW_SALE_RT
----------- ------- --------- ------ --------- -------------- 
M9B         202012         20 <null>    <null>         <null>

 

결과를 보면, 2020년 12월의 3개월전 데이터인 2020년 9월 데이터가 NULL로 채워진 것을 알 수 있습니다. 'SQL BOOSTER'나 '평생 필요한 데이터 분석' 책에서 설명한 것처럼, 분석함수는 기본적으로 조회된 결과에 대해서만 분석을 수행합니다. 그러므로 2020년 9월 데이터는 위 SQL에서 조회된 결과가 아니기 때문에 분석함수의 분석 대상이 될 수 없습니다. 위와 같은 경우는 불가피하게 앞에서 설명한 셀프 조인 방식으로 처리하는 것이 좋습니다.

이상입니다. 오늘은 정말 짧게 살펴봤습니다. ^^ 다음 글을 설명하기 위해 간단히 셀프 조인을 설명드렸습니다.

감사합니다.

 

위와 같이 데이터를 마음대로 분석해볼 수 있는 SQL을 공부하고 싶다면 아래 책을 참고해주세요~!

※ 책 소개: https://sweetquant.tistory.com/243

※ 책 미리보기: https://sweetquant.tistory.com/257

※ 완전판 E-Book

  ▶ 유페이퍼: https://www.upaper.net/ryu1hwan/1142997

  ▶ 알라딘: https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=273701425

  ▶ Yes24: http://www.yes24.com/Product/Goods/102264444?OzSrank=1

  ▶ 교보문고: https://digital.kyobobook.co.kr/digital/ebook/ebookDetail.ink?selectedLargeCategory=001&barcode=4801167630019&orderClick=LAG&Kc=

 

오늘은 오라클의 집계 함수 중에 상관관계를 구하는 CORR 함수에 대해 설명해보려고 합니다.

 

CORR 함수 하나만으로도 자신의 시스템에서 다양한 인사이트를 찾아낼 수 있습니다.

예를 들어, 비슷한 판매흐름을 갖는 고객을 찾아내거나, 유사한 추세로 팔리는 제품군들을 찾아낼 수 있습니다.

먼저, CORR 함수는 MySQL에서는 사용이 불가능합니다. 이번 글은 불가피하게 오라클을 사용해야 합니다.

 

CORR 함수를 이해하기 위해 오라클 DB에 아래와 같은 테이블을 만들어 주세요. 판매년월(SALE_YM), 아이템ID(ITEM_ID), 판매수량(SALE_QTY)로 구성된 아주 간단한 테이블입니다. 월별-아이템별 판매량을 관리하는 테이블입니다.

CREATE TABLE T_SALE_YM_01(
 SALE_YM VARCHAR2(6) NOT NULL
 ,ITEM_ID VARCHAR2(100) NOT NULL
 ,SALE_QTY NUMERIC(18,2) NOT NULL
);

ALTER TABLE T_SALE_YM_01 ADD CONSTRAINT PK_T_SALE_YM_01 PRIMARY KEY(SALE_YM, ITEM_ID);

 

 

테이블을 생성했다면, 아래 스크립트를 사용해 데이터를 입력합니다. (양이 많으니까 카피해서 쓰시면 됩니다.)

INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202001',	'M9B',	100);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202002',	'M9B',	50);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202003',	'M9B',	90);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202004',	'M9B',	10);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202005',	'M9B',	90);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202006',	'M9B',	100);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202007',	'M9B',	90);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202008',	'M9B',	50);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202009',	'M9B',	10);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202010',	'M9B',	20);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202011',	'M9B',	30);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202012',	'M9B',	20);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202001',	'M9R',	45);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202002',	'M9R',	25);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202003',	'M9R',	40);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202004',	'M9R',	15);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202005',	'M9R',	43);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202006',	'M9R',	44);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202007',	'M9R',	42);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202008',	'M9R',	25);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202009',	'M9R',	9);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202010',	'M9R',	15);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202011',	'M9R',	20);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202012',	'M9R',	9);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202001',	'W3B',	30);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202002',	'W3B',	19);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202003',	'W3B',	25);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202004',	'W3B',	3);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202005',	'W3B',	20);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202006',	'W3B',	10);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202007',	'W3B',	18);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202008',	'W3B',	18);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202009',	'W3B',	1);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202010',	'W3B',	5);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202011',	'W3B',	7);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202012',	'W3B',	5);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202001',	'G1B',	18);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202002',	'G1B',	15);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202003',	'G1B',	19);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202004',	'G1B',	18);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202005',	'G1B',	24);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202006',	'G1B',	20);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202007',	'G1B',	5);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202008',	'G1B',	16);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202009',	'G1B',	60);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202010',	'G1B',	30);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202011',	'G1B',	45);
INSERT INTO T_SALE_YM_01(SALE_YM,ITEM_ID,SALE_QTY) VALUES(	'202012',	'G1B',	45);
COMMIT;

어떤 제품들이 있는지 살펴보기 위해 아래와 같이 아이템별로 데이터 건수를 카운트해봅니다. 아래와 같이 GROUP BY를 간단하게 사용하면 되겠죠.

SELECT  T1.ITEM_ID, COUNT(*) CNT
FROM    T_SALE_YM_01 T1
GROUP BY T1.ITEM_ID
ORDER BY T1.ITEM_ID;
[결과]
ITEM_ID   CNT      
--------- --- 
G1B        12
M9B        12
M9R        12
W3B        12

 

 

'G1B, M9B, M9R, W3B' 네 개의 아이템이 있는 것을 알 수 있습니다.

이제, CORR 함수를 사용해 'M9B' 아이템과 월별로 가장 유사하게 팔린 아이템을 찾고자 합니다. 이를 위해, 가장 먼저 할일은 'M9B' 아이템의 월별 판매량과 다른 아이템의 월별 판매량을 조인하는 것입니다. 아래와 같이 SQL을 실행합니다.

SELECT  T1.ITEM_ID, T1.SALE_YM, T1.SALE_QTY
        ,T2.ITEM_ID 비교_ITEM_ID
        ,T2.SALE_QTY 비교_판매수량
FROM    T_SALE_YM_01 T1
        ,T_SALE_YM_01 T2
WHERE   T1.ITEM_ID = 'M9B'
AND     T1.ITEM_ID != T2.ITEM_ID -- 또는 T2.ITEM_ID != 'M9B'
AND     T1.SALE_YM = T2.SALE_YM -- 제품은 다르지만 월은 같은 데이터간에 조인
ORDER BY T1.SALE_YM ,T2.ITEM_ID;
[결과]
ITEM_ID    SALE_YM SALE_QTY  비교_ITEM_ID   비교_판매수량
---------- ------- --------- -------------- --------- 
M9B        202001        100 G1B                   18
M9B        202001        100 M9R                   45
M9B        202001        100 W3B                   30
M9B        202002         50 G1B                   15
M9B        202002         50 M9R                   25
M9B        202002         50 W3B                   19
M9B        202003         90 G1B                   19
... 생략 ...

 

위 SQL의 결과 데이터를 보면, 2020년 1월 M9B 아이템 한 건에, 2020년 1월의 다른 아이템 판매 세 건의 데이터가 조인된 것을 볼 수 있습니다. 아이템은 다르면서 월은 같은 경우를 모두 조인 처리했기 때문입니다.

이처럼, 데이터 분석을 위해서는 관계(FK)가 완전히 성립되지 않는 데이터간에 조인을 처리하는 경우가 많습니다.

위와 같이 조인으로 인해 M9B의 데이터가 세 배로 늘어난 것을 잘 이해해야 합니다. (SQL BOOSTER나 평생 필요한 데이터 분석에서 언급했던 내용입니다.)

 

이제, 위 SQL을 좀 변경해서 GROUP BY와 CORR 처리만 하면, M9B와 판매 추세가 가장 비슷한 아이템을 찾아낼 수 있습니다. 아래와 같습니다.

SELECT  T1.ITEM_ID
        ,T2.ITEM_ID 비교_ITEM_ID
        ,ROUND(CORR(T1.SALE_QTY,T2.SALE_QTY),6) COR_VAL
FROM    T_SALE_YM_01 T1
        ,T_SALE_YM_01 T2
WHERE   T1.ITEM_ID = 'M9B'
AND     T1.ITEM_ID != T2.ITEM_ID -- 또는 T2.ITEM_ID != 'M9B'
AND     T1.SALE_YM = T2.SALE_YM -- 제품은 다르지만 월은 같은 데이터간에 조인
GROUP BY T1.ITEM_ID ,T2.ITEM_ID
ORDER BY COR_VAL DESC;
[결과]
ITEM_ID 비교_ITEM_ID    COR_VAL  
------- --------------- --------- 
M9B     M9R               .986024
M9B     W3B               .813967
M9B     G1B              -.626263

 

CORR을 처리한 값이 1에 가까울수록 상관관계가 높습니다. 다시 말해, 위 결과에서 M9B 아이템과 M9R 아이템의 상관 관계 값이 0.98로 가장 좋습니다. M9B와 W3B는 0.81이고요, M9B와 G1B는 -0.62입니다.

상관 관계를 이해하기 위해, 아래와 같이 SQL을 하나 더 실행해봅니다. CASE를 사용해 아이템을 컬럼으로 출력합니다.

SELECT  T1.SALE_YM
        ,MAX(CASE WHEN T1.ITEM_ID = 'M9B' THEN T1.SALE_QTY END) M9B_QTY
        ,MAX(CASE WHEN T1.ITEM_ID = 'M9R' THEN T1.SALE_QTY END) M9R_QTY
        ,MAX(CASE WHEN T1.ITEM_ID = 'W3B' THEN T1.SALE_QTY END) W3B_QTY
        ,MAX(CASE WHEN T1.ITEM_ID = 'G1B' THEN T1.SALE_QTY END) G1B_QTY
FROM    T_SALE_YM_01 T1
GROUP BY T1.SALE_YM
ORDER BY T1.SALE_YM;
[결과]
SALE_YM M9B_QTY   M9R_QTY   W3B_QTY   G1B_QTY  
------- --------- --------- --------- --------- 
202001        100        45        30        18
202002         50        25        19        15
202003         90        40        25        19
202004         10        15         3        18
202005         90        43        20        24
202006        100        44        10        20
202007         90        42        18         5
202008         50        25        18        16
202009         10         9         1        60
202010         20        15         5        30
202011         30        20         7        45
202012         20         9         5        45

 

위 SQL의 결과를 엑셀로 내려서 간단한 선 차트를 그려봅니다. 아래와 같이, M9B와 M9R의 월별 판매량의 추세가 비슷한 것을 알 수 있습니다.

 

준비한 내용은 여기까지입니다.

이 기술을 잘 사용하면, 각자의 시스템에서 다양한 상관관계를 찾아낼 수 있습니다. 상관관계가 높다고 해서 인과관계가 있는 것은 아니지만, 상관관계를 이용해 새로운 인사이트를 도출해내고, 이를 응용한 마케팅 전략들을 만들어 낼 수 있을것이라 생각합니다.

감사합니다~!

 

 

지난 글에서는 메모리 품목별 수출입과 SK하이닉스, 삼성전자의 상관관계를 살펴봤습니다.

  - https://sweetquant.tistory.com/314

 

메모리 수출과 하이닉스와 삼성전자의 주가

저번 글에서는 메모리 월별 수출 금액을 간단하게 차트로 그려봤습니다. - https://sweetquant.tistory.com/313​ 파이썬과 SQL로 메모리 수출입 차트 만들기 지난 글에서는 메모리 품목 수출입 데이터를

sweetquant.tistory.com

 

본격적인 설명에 앞서, 절대 어떤 종목을 추천하기 위한 글이 아니며, 본 글로 인해 투자한 종목의 손실에 대해서는 절대 누구도 책임지지 않는다는 것을 기억해주시기 바랍니다.

데이터의 정확성 역시 일차적으로는 검토했지만, 데이터 수집 시점이나 과정에서 발생한 실수로 부정확할수도 있으니 양해바랍니다.

 

2010년부터 2021년 8월까지의 두 종목의 월별 주가와, 월별 메모리 품목별 수출금액의 상관 관계를 살펴보면 아래와 같습니다.

             DRAM      CHIP     FLASH   Samsung     Hynix
DRAM     1.000000  0.763169  0.843413  0.618390  0.698179
CHIP     0.763169  1.000000  0.769157  0.866243  0.858389
FLASH    0.843413  0.769157  1.000000  0.742537  0.810480
Samsung  0.618390  0.866243  0.742537  1.000000  0.959931
Hynix    0.698179  0.858389  0.810480  0.959931  1.000000

 

'복합구조칩 집적회로'(Chip, 수출품목코드(HS Code) = 8542323000)의 수출금액과 두 종목의 월별 주가간에 상관 관계가 높은 것을 알 수 있었습니다. 수학적인 학문이 짧은 관계로 상관 관계를 정확히 설명드릴 수 없지만, 복합구조칩의 수출과 SK하이닉스, 삼성전자의 주가는 비슷한 추세로 움직인다 정도로 해석하면 될거 같습니다.

 

이번에는 역으로 전체 종목을 대상으로 월별 주가와 복합구조칩 수출 금액의 상관 관계를 조사해봅니다. 이를 통해, 상관 관계가 가장 높은 종목을 찾아낼 수 있습니다.

아래와 같은 SQL 한 문장만 실행하면 됩니다. (아래 SQL은 오라클 기준으로 제가 별도 관리하는 데이터입니다.) 오라클은 상관관계를 구할 수 있는 CORR 집계 함수를 기본적으로 제공합니다. (MySQL을 사용한다면, 파이썬의 도움을 받아 CORR을 계산해야 합니다.)

SELECT  T2.HS_NM ,T3.STK_CD ,MAX(T3.STK_NM) STK_NM
        ,CORR(T2.OUT_AMT, T3.C_PRC) COR
        ,COUNT(*) CNT
FROM    (
		SELECT  T1.YM
		        ,T1.HS_NM
		        ,T1.OUT_AMT
		FROM    ORA_STK_USER.TRADE_YM T1
		WHERE   T1.HS_NM IN ('디램','복합구조칩 집적회로','플래시 메모리')
		) T2
		,(
		SELECT  B.YM
		        ,A.STK_CD
		        ,A.STK_NM
		        ,B.C_PRC
		FROM    ORA_STK_USER.M_STK A
		        ,ORA_STK_USER.FT_STK_YM B
		WHERE   A.STK_CD = B.STK_CD
		--AND     A.STK_NM = 'SK하이닉스'
		) T3
WHERE   T2.YM = T3.YM
GROUP BY T2.HS_NM ,T3.STK_CD
HAVING CORR(T2.OUT_AMT, T3.C_PRC) IS NOT NULL AND COUNT(*)>=100
ORDER BY COR DESC;

 

위 SQL로 아래와 같은 결과를 얻을 수 있습니다. 복합구조칩 수출과 상관 관계가 높은 종목 세 개가 모두 반도체 관련 종목입니다.

 

상관 관계가 가장 높은 케이씨와 복합구조칩 수출 금액을 차트로 살펴보면 아래와 같습니다. 차트의 그림이 거의 유사한 것을 알 수 있습니다.

 

여기서 제가 생각하는 투자 전략은, 특정 종목의 주가를 예측하지 않고 특정 품목의 수출 전망을 예측하는 겁니다. 특정 품목의 수출 전망은 산업 현황과 같은 신문 기사를 통해 얻을 수 있겠죠. 그리고 해당 품목의 수출과 상관관계가 높은 종목에 투자를 하는 겁니다. 잘될지 어떨지는 알 수 없겠죠. 그래도 주가를 예측하는 것보다는 좀 더 쉽고 안전한 투자 방법이 아닐까 싶습니다.

다시 정리하면, 여러 신문 기사를 통해 수출 전망을 예측한다. 수출이 좋을거 같은 품목의 과거 데이터를 무역 통계 사이트에서 얻어낸다. 무역 통계와 과거 월별 주가를 비교해 가장 상관 관계가 높은 종목을 찾아낸다. (여기서 수출 전망 예측도 이동평균선과 같은 추세선을 이용해 기술적 분석을 고려해 볼 수도 있습니다.)

 

이를 위해서는 데이터를 이해하고, SQL을 활용할 수 있어야 하겠죠.

 

위와 같이 주식 데이터를 마음대로 분석해볼 수 있는 SQL을 공부하고 싶다면 아래 책을 참고해주세요~!

※ 책 소개: https://sweetquant.tistory.com/243

※ 책 미리보기: https://sweetquant.tistory.com/257

※ 완전판 E-Book

  ▶ 유페이퍼: https://www.upaper.net/ryu1hwan/1142997

  ▶ 알라딘: https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=273701425

  ▶ Yes24: http://www.yes24.com/Product/Goods/102264444?OzSrank=1

  ▶ 교보문고: https://digital.kyobobook.co.kr/digital/ebook/ebookDetail.ink?selectedLargeCategory=001&barcode=4801167630019&orderClick=LAG&Kc=

 

저번 글에서는 메모리 월별 수출 금액을 간단하게 차트로 그려봤습니다.

  - https://sweetquant.tistory.com/313

 

파이썬과 SQL로 메모리 수출입 차트 만들기

지난 글에서는 메모리 품목 수출입 데이터를 다운로드해서 DB화했습니다. - https://sweetquant.tistory.com/312 메모리 품목 수출입 데이터 DB화하기 안녕하세요. 오늘은 분석에 필요한, 또는 SQL 연습에 필

sweetquant.tistory.com

 

이번에는 메모리 수출과 삼성전자, SK 하이닉스의 주가를 같이 살펴보도록 하겠습니다. 이를 통해 수출금액이 주가에 영향을 미칠까를 고민해 볼 수 있기 때문입니다.


우선, 기존에 구축했던 DB_DTECH.HIST_YM 테이블에는 2019년부터 2021년  8월 25일까지의 월별주가 데이터가 입력되어 있습니다.(아래 글에서 구축했습니다.)

  - https://sweetquant.tistory.com/304

 

2021년 전반기 영업이익 Top과 주가 흐름

지난글에서는 영업이익 증가 Top 종목을 다양하게 추출해봤습니다. https://sweetquant.tistory.com/303 2021년 전반기 영업이익 Top - 종목 추출 DART에 2021년 전반기(1월~6월) 손익계산서가 올라왔습니다.

sweetquant.tistory.com

 

여기서는 메모리 수출금액과 주가를 비교하기 위해 SK 하이닉스와 삼성전자에 대해서만 추가로 월별주가(2010년1월부터 2018년12월까지)를 입력해야 합니다. 아래 파일을 다운 받아서 DB_DTECH DB에서 실행하면 됩니다.

20210903_월별주가_삼성이랑SK하이닉스만가져오기.sql
0.03MB




데이터를 업로드 했으면, 아래와 같은 SQL로 삼성전자와 SK하이닉스의 2010년부터의 월별 종가를 구할 수 있습니다.

SELECT  B.YM
        ,MAX(CASE WHEN A.STK_NM = '삼성전자' THEN B.C_PRC END) Samsung
        ,MAX(CASE WHEN A.STK_NM = 'SK하이닉스' THEN B.C_PRC END) Hynix
FROM    DB_DTECH.STOCK_KRX A
        INNER JOIN DB_DTECH.HIST_YM B
          ON (B.STK_CD = A.STK_CD)
WHERE   A.STK_NM IN ('삼성전자','SK하이닉스')
GROUP BY B.YM
ORDER BY B.YM
[결과]
YM       Samsung     Hynix        
======== =========== ============ 
201001   15680.000   22750.000    
201002   14880.000   21000.000    
201003   16360.000   26700.000   
... 생략 ...



지난 글에서는 월별 메모리품목별 수출금액 데이터를 구했습니다. 
아래 그림을 보면, 좌측은 월별  메모리품목별 수출금액을 구하는 SQL과 결과입니다.(지난 글에서 설명한 SQL입니다.) 우측은 월별 삼성전자와 하이닉스의 주가(월말 종가)를 구하는 SQL과 결과입니다.



위 그림에서 좌측과 우측의 결과를 합쳐서 하나로 보여주기 위해서는 조인을 사용해야 합니다. 두 결과 모두 월별 데이터 집계이기 때문에 월(YM) 컬러을 사용해 조인을 처리하면 됩니다. 아래와 같이 SQL을 구현할 수 있습니다.

SELECT  T2.YM
        ,T2.DRAM ,T2.CHIP ,T2.FLASH
        ,T3.Samsung ,T3.Hynix
FROM    (
		SELECT  T1.YM
		        ,MAX(CASE WHEN T1.HS_NM = '디램' THEN T1.OUT_AMT END) DRAM
		        ,MAX(CASE WHEN T1.HS_NM = '복합구조칩 집적회로' THEN T1.OUT_AMT END) CHIP
		        ,MAX(CASE WHEN T1.HS_NM = '플래시 메모리' THEN T1.OUT_AMT END) FLASH
		FROM    DB_DTECH.TRADE_YM T1
		WHERE   T1.HS_NM IN ('디램','복합구조칩 집적회로','플래시 메모리')
		GROUP BY T1.YM
		ORDER BY T1.YM
		) T2 INNER JOIN (
		SELECT  B.YM
		        ,MAX(CASE WHEN A.STK_NM = '삼성전자' THEN B.C_PRC END) Samsung
		        ,MAX(CASE WHEN A.STK_NM = 'SK하이닉스' THEN B.C_PRC END) Hynix
		FROM    DB_DTECH.STOCK_KRX A
		        INNER JOIN DB_DTECH.HIST_YM B
		          ON (B.STK_CD = A.STK_CD)
        WHERE   A.STK_NM IN ('삼성전자','SK하이닉스')
		GROUP BY B.YM
		ORDER BY B.YM
		) T3 ON (T2.YM = T3.YM)
ORDER BY T2.YM
[결과]
YM       DRAM          CHIP          FLASH        Samsung     Hynix        
======== ============= ============= ============ =========== ============ 
201001   807931.000    322032.000    205735.000   15680.000   22750.000    
201002   768520.000    388025.000    206517.000   14880.000   21000.000    
201003   937606.000    487808.000    242982.000   16360.000   26700.000    
201004   976082.000    543395.000    253916.000   16980.000   28400.000    
... 생략 ...


위에서 HIST_YM, STOCK_KRX, TRADE_YM 은 모두 테이블입니다. 하지만, TRADE_YM을 GROUP BY 처리한 FROM절의 T2는 테이블이라기 보다는 데이터 결과 집합입니다. 마찬가지로 HIST_YM을 GROUP BY 처리한 T3 역시 데이터 결과 집합니다. 이처럼 조인은 테이블과 테이블이 아닌 데이터 결과 집합과 데이터 결과 집합 사이에 이루어질 수 있습니다. 더 나아가서 테이블 역시 데이터 결과 집합이라고 인식하셔도 됩니다.

항상 조인을 할 때는 두 데이터 집합 간의 연결 고리를 잘 찾아야 합니다. 두 데이터 집합 모두 월별로 집계되었기 때문에 월을 이용해 두 데이터 결과 집합을 연결하면 됩니다.

이번에는 위 SQL을 그대로 파이썬에 붙여서 차트를 그려보겠습니다. 삼성전자와 하이닉스 주가는 subplot으로 별도로 그려지도록 처리합니다.

import pymysql
import pandas as pd
import matplotlib.pyplot as plt

# MySQL 연결 처리
myMyConn = pymysql.connect(user='root', password='1qaz2wsx', host='localhost', port=3306, charset='utf8',
                           database='DB_SQLSTK')
myMyCursor = myMyConn.cursor()


sql = """
SELECT  T2.YM
        ,T2.DRAM ,T2.CHIP ,T2.FLASH
        ,T3.Samsung ,T3.Hynix
FROM    (
		SELECT  T1.YM
		        ,MAX(CASE WHEN T1.HS_NM = '디램' THEN T1.OUT_AMT END) DRAM
		        ,MAX(CASE WHEN T1.HS_NM = '복합구조칩 집적회로' THEN T1.OUT_AMT END) CHIP
		        ,MAX(CASE WHEN T1.HS_NM = '플래시 메모리' THEN T1.OUT_AMT END) FLASH
		FROM    DB_DTECH.TRADE_YM T1
		WHERE   T1.HS_NM IN ('디램','복합구조칩 집적회로','플래시 메모리')
		GROUP BY T1.YM
		ORDER BY T1.YM
		) T2 INNER JOIN (
		SELECT  B.YM
		        ,MAX(CASE WHEN A.STK_NM = '삼성전자' THEN B.C_PRC END) Samsung
		        ,MAX(CASE WHEN A.STK_NM = 'SK하이닉스' THEN B.C_PRC END) Hynix
		FROM    DB_DTECH.STOCK_KRX A
		        INNER JOIN DB_DTECH.HIST_YM B
		          ON (B.STK_CD = A.STK_CD)
        WHERE   A.STK_NM IN ('삼성전자','SK하이닉스')
        GROUP BY B.YM
		ORDER BY B.YM
		) T3 ON (T2.YM = T3.YM)
ORDER BY T2.YM
"""


# DataFrame에 SQL 결과 저장
df = pd.read_sql(sql, myMyConn)

# 결과 출력
print(df)

# 차트로 처리할 항목을 Series에 별도로 담는다.
dram = df['DRAM']
chip = df['CHIP']
flash = df['FLASH']
samsung = df['Samsung']
hynix = df['Hynix']
dram.index = df['YM']
samsung.index = df['YM']
hynix.index = df['YM']

plt.figure(figsize=(11, 9))

plt.subplot(311)
dram.plot(label='DRAM', title="Memory Out & Samsung & SK Hynix")
chip.plot(label='Chip')
flash.plot(label='Flash memory')
plt.legend(loc='lower left')

plt.subplot(312)
samsung.plot(label='Samsung')
plt.legend(loc='lower left')

plt.subplot(313)
hynix.plot(label='Hynix')
plt.legend(loc='lower left')

plt.show()
print(df.corr())


위의 파이썬 코드를 실행하면 아래와 같은 차트가 만들어집니다.



차트만 보고서는 수출금액이 삼성전자나 SK하이닉스의 주가에 영향을 주는지 정확히 알아 볼 수가 없습니다. 대충 보기에는 메모리 품목 중에 Chip 부분의 수출금액과 삼성전자와 SK하이닉스의 주가 움직임이 유사한 느낌은 듭니다.

차트가 유사하다는 것은 위 파이썬 코드 중에, 마지막 줄의 df.corr을 통해 확인할 수 있습니다.

df는 파이썬 모듈중에 pandas.dataframe입니다. dataframe의 corr을 수행하면, dataframe에 입력된 데이터들의 상관계수를 구할 수있습니다.
저도 수학적으로는 잘 모르지만, 상관계수가 1에 가까우면 두 계열의 수치 간에 정비례한 상관 관계가 있다고 합니다.

df.corr()의 결과를 보면 아래와 같습니다.

             DRAM      CHIP     FLASH   Samsung     Hynix
DRAM     1.000000  0.763169  0.843413  0.618390  0.698179
CHIP     0.763169  1.000000  0.769157  0.866243  0.858389
FLASH    0.843413  0.769157  1.000000  0.742537  0.810480
Samsung  0.618390  0.866243  0.742537  1.000000  0.959931
Hynix    0.698179  0.858389  0.810480  0.959931  1.000000


결과를 보면 ,Chip과 삼성의 상관계수가 0.866이고 Chip과 하이닉스의 상관계수가 0.85로 높은것을 알 수 있습니다. 그리고 삼성과 하이닉스의 상관관계는 무려 0.95나 됩니다.

이처럼 높은 상관계수를 사용하면 수익을 얻을 수 있는 투자 전략을 만들 수 있지 않을까 생각이 듭니다. 하지만 그렇게 쉽지가 않답니다. 수출 데이터는 매월 15일에 지난달 데이터가 집계가 됩니다. 그러므로 우리가 수출 데이터를 접하기 전에 주가는 올라 버렸을 가능성이 있습니다. 더욱이, 제품의 수출 전에는 '계약'이라는 단계가 필요합니다. '만약에 거대 '계약'이 있다면, 수출까지 발생하기 전에 주가는 이미 다 오르지 않을까 싶습니다. 또한 주가라는 것은 어느 한 요소만으로 결정되지는 않습니다.
그럼에도 불구하고, 조금 더 고민해볼 필요는 있을거 같습니다. 투자 전략을 못 얻더라도 SQL 실력과 데이터를 바라보는 눈을 얻을 수 있으니까요.

오늘은 여기까지입니다. 감사합니다.

 

위와 같이 주식 데이터를 마음대로 분석해볼 수 있는 SQL을 공부하고 싶다면 아래 책을 참고해주세요~!

 

※ 책 소개: https://sweetquant.tistory.com/243

※ 책 미리보기: https://sweetquant.tistory.com/257

※ 완전판 E-Book

  ▶ 유페이퍼: https://www.upaper.net/ryu1hwan/1142997

  ▶ 알라딘: https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=273701425

  ▶ Yes24: http://www.yes24.com/Product/Goods/102264444?OzSrank=1

  ▶ 교보문고: https://digital.kyobobook.co.kr/digital/ebook/ebookDetail.ink?selectedLargeCategory=001&barcode=4801167630019&orderClick=LAG&Kc=

 

+ Recent posts