2020년부터 투자를 시작하면서 그 기록과 소회를 정리해 본다. 기록하지 않으면 까먹고 같은 실수를 반복할 테니
지난 4년간의 투자 성적표는 아래와 같다.
단위(원)
연도
미국 주식
국내 주식
비고
2020
-36,971
20년 연말 투자 시작
2021
+2,348,736
코인투자 수익 +300
2022
+3,801,856
자가 마련을 위해 일부 매도
2023
+834,010
-27,861
증권사 이전으로 일부 매도
* 연금저축 투자의 경우, 실현수익이 아니므로 제외
* 투자원금대비 연평균 수익률은 +20% 정도
NIKE, BKR-B, HILTON, AMAZON과 같이 개별 주로 재미를 본 적도 있으나, 수익의 많은 부분은 QQQ, SPY와 같은 지수추종 ETF에서 나왔다. (버핏선생님의 말씀이 옳았다.) 사실 개별 이슈들은 대응도 어렵고 (미국과의 시차) 관련 재무제표 해석도 쉽지 않다. (언어의 장벽)
언어의 장벽이 없는 국내 저평가 주식도 찾지 못하는 사람이 해외 저평가 주식을 찾는 것은 더더욱 어렵다. 그러면 차트라도 볼 줄 알아야 하는데, 이동평균선의 개념도 얼마 전에 배웠다. 고로 위 성적표는 내 실력이 아닌,
지난 4년간 투자 수익은 운(시장의 상황)이 좋았기에, 발생한 수익이라 봐야 한다.
세상은 넓고 똑똑한 사람들은 많다. 똑똑한 사람들의 투자법을 살펴보고, 따라 해 보면서 나에게 맞는 투자법을 찾아가면 된다. 아래는 2024년 3월 현재, 나의 투자법이다. 정답은 아니지만 추후 바뀔 수도 있다. 변해가는 과정을 기록해 두는 것도 의미가 있다고 보기에 기록해 둔다,
1. 국장 투자는 지양한다.
지양하는 이유는 아래와 같다.
- 주주친화적인 기업이 많지 않다.
- 한국 경제가 장기적으로는 우상향 하지 않을 것 같다. (내 예상이 틀리길 바란다.)
- 국장투자를 하게 된다면, 아래 질문들을 다시 한번 해보고 답을 찾아보기로
① 취준생시절로 돌아가 해당 회사로 입사기회가 주어진다면 들어갈 회사인가?
(내 돈을 맡기는데, 나보다 똑똑한 사람들이 있는 회사여야 하지 않을까)
② 주주환원, 주주친화적인가?
농담삼아 올려본 짤이긴 한데, 웃어넘길 순 없는.
2. 모르는 산업 분야는 투자하지 않는다.
- 특히 바이오, 테마주
3. 중국주식 및 중국 관련 투자는 지양한다.
- 한번 항셍에서 물려봤으니 깨달을 때도 되었다.
- 장기적으로 중국의 미래가 밝을까? 아닐 것 같다. (이건 내 예상이 맞길 바란다.)
4. 잃지 말자.
- 투자는 리스크를 수반하고 수익률은 리스크에 비례한다. 그렇기에 투자는 항상 손실의 가능성을 안고 시작한다.
- 절대 잃지 않는 투자는 없지만, 큰 리스크를 가진 투자는 지양하도록 한다.
- 메냔 목표 수익률은 시장 수익률을 조금 상회하는 선으로 잡는다.
5. BUY TOP3, 지수추종
- 개별주에 투자를 하고자 한다면, 각 산업에서 TOP3안에 드는 기업을 투자하도록 한다.
(산업별 TOP3 가 바뀔 수도 있으므로 주기적인 리밸런싱은 필수, 이는 투자 guru이자 멘토 형님의 투자법이기도 하다.)
- 지수추종 ETF 위주로 투자. 배당 위주의 투자는 어느 정도 자산을 이룬 다음 고려하도록 한다.
샘 젤은 리츠(REITs)의 아버지라 불리우며, 부동산으로 시작해 다양한 산업군에서 기업가, 투자자로서 큰 성공을 거둔 인물이다. 해외의 유명세에 비해 우리나라에서는 크게 알려지지 않은 인물이다. (나 또한 책을 통해 알게 되었다.)
분량은 그리 많지 않다. (300페이지가 되지 않음.) 그렇기에 부담 없이 가볍게 읽기 좋은 책이라 할 수 있다.
물론 일부 내용에서는 친절하지 않은 회계용어들이 등장하지만, 저자의 거래를 설명하기 위해서는 빠질 수 없는 부분임을 감안해야 한다.
항상 책을 읽기 전, 서론 혹은 소개말을 꼭 읽고 넘어가는 편인데 서론의 맺음말이 참 인상 깊었다.
(생각해보면 앞으로 계속 나올 저자의 유머감각을 미리 암시하는 부분이 아닐까.)
'저는 할 일도 많고 할 말도 많습니다. 하루하루가 모험이죠. 여기 제 이야기를 시작합니다. 재미있게 즐기세요."
인상 깊은 구절들을 정리해본다.
p.10
(중략) 모두의 의견을 들으려고 합니다. 하지만 그 이후의 길은 제가 결정합니다. 저는 명확성을 찾고 불분명한 점이 있으면 더 많은 정보를 수집하려고 노력합니다. 명확성을 찾는 것은 다양한 뉴스를 읽고, 새로운 법률을 이해하거나, 지구 반대편에 있는 누군가와 만나는 것을 의미할 수 있습니다. 중요한 것은 가정을 하지 않는다는 것이죠. (중략) 일단 제 의견을 형성하면 행동으로 옮길 수 있을 만큼 제 관점을 신뢰해야 합니다.
p.37
희소성이 있는 곳에서 가격은 문제가 되지 않는다.
p.47
만약 어떤 일을 할 수 없다는 사실을 알아차리지 못한다면 그 일을 하는 데 방해가 되는 요소들은 크게 줄어든다.
-> 내 경험상, 너무 많이 알아가다 보면 지엽적인 시야를 갖고 판단하게 되는 경우가 많았다.
p.79
거래는 완전히 끝날 때까지 끝난 게 아니다.
"It ain't over till it's over."
p.97
단순하게 유지해라. 한 단계가 아닌 네 단계를 거치는 시나리오는 실패할 기회가 세 번 더 있다는 것을 뜻한다.
p.112
솔직히 제한된 경쟁을 대체할 수 있는 것은 없다. 당신이 아무리 천재라도 경쟁이 치열하면 소용이 없다. 나는 내 커리어에서 경쟁의 파괴적인 결과를 피하기 위해 노력해 왔다. 경쟁은 사람들의 평가를 왜곡시킨다. 구매자들의 경쟁이 치열해지면 자산에 대한 수요가 부풀려지고 가격은 이성을 넘어선다.
p.156
부동산은 단순히 무생물인 건물에 관한 것이 아니다. 부동산은 종종 국가의 맥박을 반영한다.
-> 앞 내용에서는 미국의 사회변화 과정에서 어떠한 부동산들이 선호도가 올라가고 내려가는지 사례를 들어 설명하고 있다. 이를 보면 '한강뷰'에 대한 선호도 증가, 대형 평수에 대한 선호도 하락 등으로 대표되는 국내 부동산 사례가 떠오른다.
마지막으로 저자가 강조한 핵심 철학을 정리해 본다.
- 방향을 바꿀 준비를 해라 (Be ready to pivot)
- 간단하게 해라 (Keep it simple)
- 항상 눈을 크게 뜨고 마음을 열어라 (Keep your eyes and mind wide open)
'세이노의 가르침'에서 추천도서로 있던 책. 지금은 절판되었고 도서관에서도 항상 대출 중이었기에 오랜 기간 대기 끝에 드디어 읽어볼 수 있었다. 20년이 넘은 책인지라 (2001년 출판) 지금과는 맞지 않는 부분들도 일부 보이지만 (특히 회사채에 대한 투자 부분, 내 생각이 틀렸을 수도 있다.) 그 외 다른 내용들은 공감되는 부분도 많고 많은 가르침을 내게 주기도 한 책이다. 기회가 된다면 주변 사람들도 일독을 권하고 싶은 책이다. 내용이 어렵지도 않고 분량도 그리 많지 않다.
책을 읽어보면서, 인상 깊었던 또는 되새겨볼 만한 내용들을 아래에 옮겨보았다.
P.68
장기계획보다는 1년 단위의 계획을 세울 것.
P.90 - 91 근로소득과 불로소득을 구분해야 한다. 부자란 불로소득이 근로소득보다 높은 사람을 의미한다. 샐러리맨은 미미한 수준이라 하더라도 끊임없이 근로소득을 밑천 삼아 불로소득을 늘리려는 노력을 해야 한다. 근로소득을 높인 뒤, 불로소득을 창출해나가야 한다. 이를 무시하고 불로소득만 바란다면 투기로 이어진다.
P.105 경제 지식도 돈을 버는 데 중요하다. 당신의 지적 인프라를 구축하는 차원에서만 말이다. 그러나 인프라가 직접 돈을 벌어주는 것은 아니다. 인프라는 토대요, 바탕일 뿐이다.
P.106 - 107 사고를 현재 진행형으로 해야 한다. 과거형 사고를 가지고 과거에 이랬느니 저랬느니, 하는 식의 얘기는 옳지 못하다. 시장에서 투자를 통해 돈을 버는 사람들은 수요와 공급이 만나는 지점 이전에 움직이는 사람들.
P.110 검색능력과 분석능력, 덧붙여 실전 경험.
P.133 다른 업권에 있는 사람들을 많이 만날 것.
P.141 최소 한 달의 한 번은 본인의 투자상황을 점검하고, 재산 상태를 분석하는 시간을 가질 것.
P.162 모으는 기간을 최대한 짧게 하고 굴리는 기간을 길게 하는 게 금리 면에서나 돈의 운용측면에서나 기회가 왔을 때 신속히 대응할 수 있다는 점에서 모두 유리하다.
P.202 경기변동과 관련해 투자계획을 세울 것. 불황기에는 금융상품과 단기 유동성 위주로 돈을 굴리고 경기의 터닝 포인트가 이뤄지면 주식과 부동산 투자의 비중을 늘려야 한다. (특히나 MMF 같은 단기 유동성 자금은 30~40% 비중을 둘 것)
P.208 높은 수익은 변곡점이 생기는 곳에서 발생한다. 그 변곡점에 즉각 대응할 수 있는 포트폴리오를 구축하고 있어야 한다. (IMF 때 싼 가격으로 아파트를 매수했던 사람들이 좋은 예시) -> 결국 유동성이 있는 포트폴리오가 중요하다.
P.220 개인투자자들이 리스크를 줄일 수 있는 길을 두 가지다. 하나는 지식을 쌓는 것, 지식이 없다면 자신보다 나은 사람에게 물어보는 것이다. 다른 하나는 최악의 경우를 가정하는 것이다. (중략) 두려워하라, 그리고 두려움에 맞서기 위해 빠져나갈 구멍을 생각하라.
P.223 진부하지만 아래 내용들은 하지 말아야 하는 것들이다. - 빚내서 주식투자하지 말라. - 무리한 대출로 집을 사지 말라 - 한 곳에 올인하지 말라. - 목표수익률을 지나치게 높게 잡지 말라. - 할부 좋아하지 말라. - 돈을 잃더라도 지나치게 연연하지 말라. - 신문기사를 지나치게 믿지 말라. - 이해가 안 되면 투자하지 말라. - 남의 얘기를 지나치게 믿지 말라. 무언가를 정리할 때 가장 좋은 방법은 '적는 것'. 과거 실패 사례를 정리해 보며 복기할 것.
P.228 투자할 때 항상 최악의 상황을 머릿속에 그려라. 실패를 하면 그 이유를 정확히 분석하라. 투자의 세계에서는 최악의 상황을 가정한 보수적인 투자자들이 늘 이겨 왔다.
중요한 건 부모님이 나를 어떻게 키웠느냐와 상관없이 나 자신을 받아들이고 뚜벅뚜벅 걸어나가는 것이다. 다른 이들을 탓하며 사는 건 옳지 않다. 문제 해결에도 전혀 도움이 되지 않는다. 나는 이미 성인이 된 지 십수년이 지났으며, 부모님의 보살핌을 받으며 살아온 시간보다 내가 혼자 결정하며 살아온 날이 더 길다. 지금의 나를 받아들이는 것이 중요하다. 부모님은 순간순간 최선을 다해 나를 키웠고, 그분들의 실수는 이제 잘 기억나지 않는다. 어린 시절 내게 새겨진 무늬는 빛바랜 지 오래다. 스스로 나만의 길을 걸어가는 것이 최선이다.
p.40
꿈이 있는 것, 이루고 싶은 미래가 있는 것은 좋다. 그러나 '매일 살아내는 삶의 합이 내 인생'이라는 말처럼, 내가 살아내는 현재와 순간들이 결국 나의 미래가 되는 것이다. 나는 내가 가지고 있지 않은 능력을 원했고, 그 능력이 있어야만 이룰 수 있는 꿈을 꿨다. 따라가면 멀어지는 무지개처럼, 희미해지는 꿈을 좇으며 절망했다. 내가 가진 것에 집중하지 못했다. 현재 내가 할 수 있는 것, 가진 것, 느끼는 것 모두 무시한 채 달려왔다.
p.123
아이를 키운다는 것은 어린 나를 대면하는 일이기도 하다. 아이의 모습에서 나를 보고 나의 아픔을 본다. 아이가 울 때 안아주며 어린 시절 혼자 울던 나를 함께 안아준다.
p.140
물론 부모님은 나를 사랑으로 키워주셨다. 부족함 없이 키우려 노력했고 최선을 다하셨다. 하지만 누구에게나 빈자리는 늘 있기 마련이며, 그 자리는 스스로 채워야 하는 것이다. 부모님에 대한 원망으로 그 시절을 그대로 남겨두는 것이 아니라 다 자란 내가 어린 나를 안아주면 되는 것이다.
p.141
'있으면 좋은 것'이 아닌 '없으면 안되는 것'을 결정해야 한다. 나는 이미 없으면 안되는 것은 충분히 가지고 있다.
금융업권에 있으면서 투자은행에 대한 이야기는 참 많이 들었지만, 정작 투자은행이 무슨 일을 하는지는 잘 모르고 있었다.
쉽고 간결한 표현으로 투자은행의 종류, 투자철학, 역할, 내부 조직 구조 등을 알려주고 있다. 또한 투자은행 취업을 준비하는 이들을 위한 자료들도 부록으로 포함되어 있어 IB 쪽을 준비하는 취준생에게도 일독을 권한다.
이 책을 통해 알게 된 내용을 정리해 보고 인상 깊은 문구들을 기록해 본다.
우리에게 익숙한 상업은행(Commercial Bank)은 예대마진을 통해 주수익을 창출. (물론 수익원은 다양하고 많지만.)
투자은행(Investment Bank)은 자금 조달/중개 및 금융자문으로 주수익을 창출. (기관과 기업을 대상으로 하는 B2B)
투자은행의 업무를 간단히 정리해 보면 아래와 같다.
1. 자금조달
① 부채를 통한 자금 조달 (채권발행, 투자은행으로부터 직접 대출)
- 채권발행 시 리스크를 낮추고자 언더라이팅(Underwriting) 과정을 거치는데, 언더라이팅에는 크게 2가지 방법이 존재함.
* 발행된 채권을 투자은행이 전량매수(Bought Deal) : 채권의 시세차익(Spread)을 노림.
* 채권중개에 최선을 다하겠다는 협약(Best Efforts Deal)
- 기업들의 부채 대환을 리파이낸싱(Refinancing)이라 한다.
② 주식을 통한 자금 조달
- 기업공개(IPO, Initial Public Offering) 업무 주관
* IPO시 주식의 적정가격, 기업가치를 책정
* 언더라이팅 업무 수행
*IPO에 대한 홍보/마케팅 수행 (Roadshow)
- 유상증자(Folow-on Offering) 업무 주관
유상증자 업무에서는 이미 기업 가치를 반영하는 가장 좋은 지표(주가)가 있으므로, 홍보/마케팅 업무에 중점을 둔다.
2. 금융자문
① 재무 구조 개선(Restructuring Advisory)
- 부채 및 기업구조 등에 대한 자문
② 인수합병(Merge and Acquistions, M&A)
합병(Merge)은 두 회사가 하나로 합쳐지는 것을 의미하며, 인수(acquisition)는 한 회사가 다른 회사를 구매하는 것을 의미한다, 투자은행은 인수합병 과정에서 회사의 적정 가치 판단, 회사에 대한 정보 수집 및 제공, 구매자와 판매자 연결, 회사의 매각(경매) 절차 진행 등의 업무를 수행한다.
프런트 오피스(Front Office) : 투자은행의 핵심 상품들을 다루는 부서
백 오피스(Back Office) : 프런트 오피스를 보조하는 부서
콜 프리미엄(Call Premium) : 원금을 미리 상환함에 따라 받는 페널티
전환사채 : 약속된 주식 수만큼 전환이 가능한 채권.
투자은행의 업무형태(구조)에 따른 분류는 아래와 같다.
1. IB(Investment Banking) : 상업은행 요소 없이, 투자은행 업무만 수행
2. CIB(Commercial Investment : 상업은행 업무를 겸하는 투자은행
3. UB(Universal Banking) : 투자은행은 아니지만 일부 투자은행 업무를 담당하는 부서가 있는 은행.
상품 및 고객에 따른 투자은행 분류는 아래와 같다.
1. 벌지 브래킷(Bulge Bracket, BB) : 자산과 규모가 큰 투자은행
2. 미들 마켓(Middle Market, MM) : BB보다는 규모가 작은 투자은행(적은 자산규모로 인하여 자본중개보다는 자문 업무에 집중하는 경우가 많다.)
3. 부티크(Boutique) : 자문업무에만 집중하는 소규모 투자은행
투자는 본질적으로 '가치의 상승'을 노리고 '비용을 지불'하는 행위이다. 이 두 가지 요소 중 하나가 빠진다면 그건 투자라고 할 수 없다.
p.173
시장가치(Market Value) : 시장에서 거래되는 가격, 수요와 공급에 의해 결정되는 가치
내재가치(Intrinsic Value) : 본질에 의해 결정되는 가치, 시장환경에 영향을 받지 않는다.
투자도 결국은 내재가치와 시장가치를 비교하는 행위이다.
구매하는 자는 시장가치가 내재가치보다 낮을 때 구매하고, 판매하는 자는 시장가치가 내재가치보다 높을 때 판매한다.
시장가치가 반드시 내재가치를 나타내지는 않지만, 내재가치에 대한 예상으로 형성되는 것이 시장가치이다.
주식가치 : 한 기업이 가진 '모든 것의 가치' 중 주식 투자자에게 해당하는 가치 (상장기업은 시가총액을 의미)
기업가치 : 한 기업이 가진 '핵심 사업 요소'의 가치이며 모든 투자자에게 해당하는 가치
(핵심 사업 요소란 한 기업이 사업을 계속하기 위해서 필요한 중요 요소를 의미)
현금은 핵심 사업 요소가 아니므로 주식가치에서 빼준다.
즉,'기업가치 = 주식가치 - 현금'의 공식이 성립한다.
기업가치는 모든 투자자(부채투자자, 우선주 투자자)를 포함한다. 모든 투자자들의 지분에 해당하는 요소들을 포함해야 하므로,'기업가치 = 주식가치 - 현금 + 부채 + 우선주'의 공식이 성립한다.
또한 주식 투자자 외 다른 투자자를 고려하기 위해 NCI를 더하고 EI를 뺀다.
'기업가치 = 주식가치 - 현금 + 부채 + 우선주 + NCI - EI'
(NCI : 비지배주주지분(Non-Controlling Interests)을 의미하며, 특정 회사의 지분을 50% 이상 소유할 때 '소유하지 않은 부분'을 의미한다.)
(EI : 지배주주지분(Equity Investements)을 의미하며, 특정 회사의 지분을 50% 미만 소유하고 있을 때, '소유하고 있는 부분'을 의미한다.)
마지막으로 연금, 추징금과 같은 유사부채를 더하면 기업가치에 대한 공식이 아래와 같이 완성된다.
'기업가치 = 주식가치 - 현금 + 부채 + 우선주 + NCI - EI + 유사부채'
가치평가법에는 절대적 가치평가 / 상대적 가치 평가로 나뉜다.
절대척 가치평가법은 정확하고 논리적인 평가가 가능하지만, 정보가 부족한 경우 사용하기가 어렵다.
(예 : 현금흐름할인법(DCF) : 현금흐름을 할인하여 기업의 가치를 계산
배당할인법(DDM) : 배당금을 할인하여 주식의 가치를 계산
* 자산의 가치는 그 자산이 창출하는 미래 수식이 가지는 현재 가치의 총합임을 가정함.)
상대적 가치평가법은 빠르고 간단하지만, 비교 대상 설정에 문제가 있을 경우 주관성이 크게 도입되는 이슈가 있다.
(예 : 트레이딩 멀티플(Trading Multiples) : 시장에 거래되는 회사들과 비교하는 방법
트랜잭션 멀티플 (Transaction Multiple) : M&A딜을 통해 비교하는 방법 )
보통 여러 가치평가법을 모두 활용하여, 가치의 범위를 구하는 것이 일반적이다.
책에서는 DCF, DDM에 대한 자세한 설명에 많은 페이지를 할애하고 있지만, 여기서는 간략한 개요만 적고 추후 필요한 일이 생길 때 다시 살펴보기로 한다.
베타(Beta) : 주식이 시장의 움직임과 비교해 얼마나 민감하게 반응하는지 보여주는 변동성의 지수를 의미한다.
시장 1++, 주식 1++ 인 경우, 해당 주식의 베타계수는 1
시장 1++, 주식 2++ 인 경우, 해당 주식의 베타계수는 2
시장 1++, 주식 1-- 인 경우, 해당 주식의 베타계수는 -1
* 리스크를 조절하는 것은 단순히 분산투자를 하는 것과는 다르다. 각 투자 상품과 종목의 변동성을 고려해 비중을 조절하고, 그 비중을 어느 정도 유지하면서 투자하는 것이다. (중략) 일반 투자자의 경우 제한된 정보를 가지고 거래해야 하기 때문에 더욱 위험하다. 투자은행의 접근 방식을 이용해 투자 상품별 위험성을 고려해 포트폴리오를 분배하는 시스템을 짜는 것이 중요하다. (중략) 시스템을 만들고 투자하면 사람이라면 누구나 겪는 충동과 감정의 기복을 어느 정도 제어할 수 있기 때문이다.
p.321 - 322
* 투자란 '가치 상승을 위해 값을 지불'하는 행위인데 가치에 대해 고려하지 않고 돈을 지불하는 것은 그야말로 도박과 같다. 시장에 거래되는 모든 자산은 장기적으로는 어느 정도 내재가치의 방향성을 따라가게 된다.
1. SUBQUERY의 종류 - 하나의 질의문 내부에 하나 이상의 다른 질의문이 포함되어 그 결과를 이용할 때, 내부에 포함된 쿼리문을 의미한다.
(Scalar Subquery, Inline View, Nested Subquery, Correlated Subquery 등이 Subquery의 속한다.) - GROUP BY를 제외하고는 모든 절에는 SUBQUERY가 위치할 수 있다.
- Scalar Subquery는 SELECT, FROM, WHERE, ORDER BY에 모두 존재 가능. - Inline View는 FROM절에만 존재하며, - Nested Subquery, Correlated Subquery는 WHERE절에만 존재
2. NESTED SUBQUERY - 서브쿼리가 WHERE 절에서 사용된 경우, Nested Subquery라 함. - NestedSubquery가 Main Query보다 먼저 실행될 때 속도를 낼 수 있는 유형임 - 단, 서브쿼리 쪽에서 조회하는 Main Query 값에 인덱스가 없으면 서브쿼리는 먼저 실행되지 않음
ex) SELECT empno, ename FROM emp WHERE deptno = (SELECT deptno FROM dept WHERE dname = 'SALES'); -> deptno에 인덱스가 있다면 Nested Subquery가 먼저 실행, 다만, 인덱스가 있더라도 인덱스를 사용할 수 없는 환경(!= 연산자 등)이라면 Nested Subquery가 먼저 실행되지 않음.
위 쿼리의 실행계획은 아래 그림과 같다. Subquery 3번이 먼저 실행된다.
3. CORRELATED SUBQUERY
- 서브쿼리가 WHERE절에서 사용되고 메인 쿼리에서 데이터를 하나씩 읽을 때마다 서브쿼리가 실행되어 데이터를 리턴하는 서브쿼리를 의미함. - 메인쿼리에서 데이터를 읽고 있는 Row 수만큼 서브쿼리가 실행됨. (메인쿼리보다 나중에 실행된다.)
ex) SELECT ename, empno FROM emp WHERE EXISTS (SELECT 'X' FROM dept WHERE dept.deptno = emp.deptno AND dept.dname = 'SALES'); dept.deptno = emp.deptno 때문에 emp에서 데이터를 먼저 가져온다.
위 쿼리의 실행계획은 아래와 같다.
4. SCALAR SUBQUERY
- 단 하나의 데이터와 단 하나의 칼럼에 대한 정보를 리턴함 - Scalar Subquery 사용 위치 (Select List 항목, 함수의 인자,WHERE절의 조건, Order by 절, case조건절, case 결과절) - S결과 값이 0개인 경우, NULL / 결과 값이 2개인 경우, ORA-1427를 반 - 메인쿼리보다 나중에 실행된다.
ex) SELECT e.ename, d.dname FROM emp e, dept d WHERE e.deptno = d.deptno;
- 위 쿼리는 아래와 같이 변경 가능.
SELECT e.ename (SELECT d.dname FROM dept d WHERE d.deptno = e.deptno) AS dname FROM emp e;
- 아래와 같이 PL/SQL로 함수(User Define Function)를 만들 수도 있음.
CREATE OR REPLACE FUCTION f_dnm(a_dno IN dept.deptno%TYPE) RETURN VARCHAR2 RESULT_CACHE RELIES ON(dept) AS h_dnm dept.dname%type := NULL; BEGIN SELECT dname INTO h_dnm FROM dept WHERE deptno = a_dno;
RETURN h_dnm; END; /
SELECT ename, f_dnm(deptno) AS dname FROM emp;
5. ROLLUP() & CUBE()
ROLLUP() : 데이터의 총계를 나타낼 때 사용하는 함수
ROLLUP 함수의 예시
CUBE() : 데이터의 소계를 나타낼 때 사용하는 함수
CUBE 함수의 예
예시A) SELECT d.dname, e.job COUNT(*) AS "Empl Cnt", SUM(e.sal) AS "Tot Sal" FROM dept d, emp e WHERE d.deptno = e.deptno GROUP BY d.dname, e.job -> R ORDER BY 1, 2;
예시A 쿼리의 결과
예시B) SELECT d.dname, e.job COUNT(*) AS "Empl Cnt", SUM(e.sal) AS "Tot Sal" FROM dept d, emp e WHERE d.deptno = e.deptno GROUP BY ROLLUP(d.dname, e.job) ORDER BY 1, 2;
예시B 쿼리의 결과
예시C) SELECT d.dname, e.job COUNT(*) AS "Empl Cnt", SUM(e.sal) AS "Tot Sal" FROM dept d, emp e WHERE d.deptno = e.deptno GROUP BY CUBE(d.dname, e.job) ORDER BY 1, 2;
예시C 쿼리의 결과
6. GROUPING SETS()
- 여러 개의 GROUP BY 쿼리를 동시에 실행한 것과 같은 결과를 나타냄 - GROUPING SETS()로 ROLLUP(), CUBE(), ROLLUP()&CUBE() 구현이 가능함.
ex) SELECT d.dname, e.job COUNT(*) AS "Empl Cnt", SUM(e.sal) AS "Tot Sal" FROM dept d, emp e WHERE d.deptno = e.deptno GROUP BY GROUPING SETS((d.dname, e.job), (d.dname), ()) ORDER BY 1, 2; -> GROUP BY ROLLUP(d.dname, e.job)과 동일한 결과
SELECT d.dname, e.job COUNT(*) AS "Empl Cnt", SUM(e.sal) AS "Tot Sal" FROM dept d, emp e WHERE d.deptno = e.deptno GROUP BY GROUPING SETS((d.dname, e.job), (d.dname), (e.job), ()) ORDER BY 1, 2; -> GROUP BY CUBE(d.dname, e.job) 과 동일한 결과
7. ANALYTIC FUNCTIONS
- 행과 행 간의 관계를 정의하거나 비교, 연산하기 위해 사용함
SELECT Analytic_Function (arguments) OVER ([Partition By 칼럼] [Order by 절] [Windowing 절]) FROM 테이블명 WHERE ;
Argument : 함수에 따라 0 ~ 3개의 인자가 지정됨. Partition By 절 : 전체 집합을 기준에 의해 소그룹으로 나눔. Order By 절 : 어떤 항목에 대한 정렬 기준을 기술함. Windowing 절 : 함수에 의해서 제어하고자 하는 데이터 범위를 정의함.
*종류 그룹 내 데이터 순위 : ROW_NUMBER, RANK, DENSE_RANK 그룹 내 비율 : RATIO_TO_REPORT
* EMP 테이블에서 부서(DEPTNO) 별로 급여가 높은 사람 순으로 순위를 구하기 위한 SQL ex) SELECT deptno, ename, sal, ROW_NUMBER() OVER(PARTITION BY deptno ORDER BY sal DESC) As rno, RANK() OVER(PARTITION BY deptno ORDER BY sal DESC) as rk, DENSE_RANK() OVER(PARTITION BY deptno ORDER BY sal DESC) as drk FROM emp;
곱집합 연산을 생각하면 된다. 특정 테이블의 데이터를 필요한 만큼 복사(copy) 하기 위한 방법
Cartesian Product 예시
* Cartesian Product가 발생하는 경우 - WHERE 절이 없는 조인 수행 - WHERE 절은 있으나 테이블 조인을 위한 조건 없이 조인을 수행
* '데이터 복제'라는 개념을 활용하기 위해 사용하지만, 잘못 사용하게 되면 오히려 데이터를 부풀리는 원인이 되기 때문에 퍼포먼스를 오히려 나쁘게 할 수 있다.
2. 사용하는 방법
위 그림에서 ?에는 Table이 올수도, View가 올수도 있다.
Cartesian Product는 아래 3가지 방법 중에 하나를 선택하여 사용한다. ① COPY_T, IMSI_T, DUMMY_T 와 같은 temporary Table을 활용함 (Cartesian product 만을 위한 전용 테이블을 생성해서 사용) ② DUAL과 같은 dummy 테이블을 사용 ③ 타 SQL에서 사용하고 있는 Table 활용 및 ROWNUM을 사용한다. (주로 Master 테이블을 사용)
3. Cartesian Product 적용 예제 - UNION으로 연결된 각각의 SQL이 읽고 있는 데이터가 전부 같을 경우, 데이터 복제와 같은 개념을 활용하기 위해 사용 - 데이터 구조 변환을 통해 사용자가 요청한 구조대로 데이터를 조회할 때 사용
ex)
SELECT '직군별' AS class, job, COUNT(*) AS cnt FROM emp GROUP BY job UNION ALL SELECT '부서별' AS class, TO_CHAR(deptno), COUNT(*) FROM emp GROUP BY deptno UNION ALL SELECT '총인원' AS CLASS, NULL, COUNT(*) FROM emp
모두 emp 테이블을 참고하며, WHERE절이 없는 쿼리들을 UNION ALL 시킨 것. 각 SQL문이 읽어들이는 데이터가 같을 때, 데이터 복제를 통해 튜닝을 진행해야함. (WHERE절이 있다면, WHERE절도 모두 같아야 한다).
Cartesian Product를 사용하면 아래 쿼리로 수정이 가능함.
SELECT DECODE(rn, 1, '직군별', 2, '부서별', '총인원') AS class
DECODE(rn, 1, job, 2, deptno),
SUM(cnt)
FROM (SELECT job, deptno, COUNT(*) AS cnt
FROM emp
GROUP BY job, deptno),
(SELECT ROWNUM AS rn
FROM (SELECT LEVEL FROM dual CONNECT BY ROWNUM <=3 ))
GROUP BY rn, DECODE(rn, 1, '직군별', 2, '부서별', '총인원'), DECODE(rn, 1, job, 2, deptno);
-> 수정된 쿼리는 emp 테이블을 한 번만 읽은 효과가 있다.
그림처럼 변환하기 위해서는 아래와 같은 쿼리를 사용
SELECT a.ename, b.qtr DECODE(b.qtr, 1, a.q1, 2, a.q2, 3, a.q3, a.q4) AS sl FROM (SELECT e.name, q1, q2, q3, q4, ROWNUM FROM emp_sal) a, (SELECT ROWNUM AS qtr FROM (SELECT LEVEL FROM DUAL CONNECT BY ROWNUM <=4)) b ORDER BY 1, 2;
- 연결 고리에 인덱스가 전혀 없는 경우에 사용. - 대용량의 자료를 조인해야 함으로써 인덱스 사용에 따른 랜덤 엑세스의 오버헤드가 많은 경우에 사용.
* 튜닝을 하기 위해서는 각 테이블로부터 데이터를 빨리 읽어 들이도록 해야 함. 조인하기 전 정렬을 빠르게 하기 위해 메모리(SORT_AREA_SIZE)를 최적화해야 한다.
- 조인하고자 하는 각 테이블에 대해서 독립적으로 데이터를 읽어 들일 때, 이를 얼마나 빠르게 할 것 인가가 중요함 (FTS 인 경우, I/O 시 읽어 들이는 blocks 수를 늘리는 방법을 고려해 볼 수 있음.) - 각 테이블로부터 읽혀진 데이터를 연결고리에 대해 정렬을 수행할 때 이를 얼마나 빠르게 할 것인가가 중요하다.
2. SORT / MERGE JOIN의 수행절차
① Driving, Driven Table이 없으므로 각 테이블에 대해 동시에 독립적으로 데이터를 먼저 읽어 들임 ② 읽혀진 각 테이블의 데이터를 조인을 위한 연결 고리에 대하여 정렬을 수행함. (모든 테이블에 대한 정렬이 끝나야 다음 단계로 넘어갈 수 있음) ③ 정렬이 모두 끝난 후에 조인 작업이 수행됨
* 튜닝을 하기 위해서는 각 테이블로부터 데이터를 빨리 읽어 들이도록 해야 한다.
또한, 정렬을 빠르게 하기 위해 메모리(SORT_AREA_SIZE)를 최적화해야 한다.
예시) color만 index가 존재하는 경우 SELECT /*+USE_MERGE(a b)*/ a.color, ... , b.size, ... FROM table_a a, table_b b WHERE a.joinkey_a = b.joinkey_b AND a.color = 'RED' AND b.size = 'MED';
-> b테이블은 Full Table Scan을 타게 된다.
3. SORT / MERGE JOIN 이 불리한 경우
- JOIN하는 테이블의 정렬속도가 차이가 클 경우, 대기 시간이 크게 발생하므로 성능상 불리하다. - 각 테이블로 읽어 들인 데이터의 크기가 매우 큰 경우 - 각 테이블로 읽어 들인 데이터를 조인 전, 정렬하는데 이때 정렬할 데이터가 지나치게 큰 경우
4. SORT / MERGE JOIN의 장단점
장점 : 연결고리에 인덱스가 생성되어 있지 않는 경우에 빠른 조인을 위하여 사용 가능 단점 : 각 테이블로부터 읽어 들인 데이터의 크기가 매우 큰 경우 성능상 불리함
(데이터 정렬 소요시간, 데이터 스캔 소요시간)
5. HASH JOIN
- NESTED LOOPS JOIN은 인덱스 사용에 의한 랜덤 엑세스 오버헤드 이슈가 있음. - SORT/MERGE JOIN은 정렬작업으로 인한 오버헤드 이슈가 있음.
- SORT/MERGE JOIN과 비교해 보면, 각 테이블에 대한 처리를 독립적으로 하는 것은 같지만, HASH JOIN에서는 Driving Table이 존재함. - 읽어 들인 각 테이블의 데이터를 서로 조인하기 위해 해싱(Hashing)을 이용해서 해시 값을 만듦 (Driving Table을 먼저 읽어 들여 해시값을 메모리에 올려두는 작업을 수행) -> 해시 값으로 조인을 수행함.
* Driving table을 잘 결정해야함. (적은 양의 데이터를 가진 테이블을 선택하는 게 중요) 각 테이블로부터 데이터를 읽어 들일 때, 빨리 읽을 수 있도록 함 메모리(HASH_AREA_SIZE)를 최적화하는게 중요함. (보통 SORT_AREA_SIZE의 2배를 기본 값으로 설정)
6. HASH JOIN의 수행절차
① Driving Table 결정 ② Driving Table의 연결조건 칼럼 해싱 및 해시 값 생성 ③ 읽어 들인 데이터와 해싱에서 만들어진 해시 값을 메모리에 저장 ④ Hash Join이 적용될 테이블의 연결조건 칼럼 해싱 및 해시 값 생성 ⑤ 읽어 들인 데이터와 해싱에서 만들어진 해시 값을 메모리에 저장 ⑥ 각 테이블에 조인할 데이터가 있는지, 조인하고자 만들었던 해시 값 간에 충돌이 있는지 확인
(충돌이 있을 경우, 2차 해싱을 수행) ⑦ 각 테이블의 해시값을 '='로 조인을 수행함.
7. HASH JOIN의 장단점 - Hash Bucket이 조인 집합에 구성되어 해시 함수 결과를 저장해야 하는데 이러한 처리에는 많은 메모리와 CPU 자원을 소모하게 됨. - 기본적으로 HASH_AREA_SIZE에 지정된 크기만큼의 메모리가 할당되어 사용됨. * 조인을 수행하기에 메모리가 부족하다면 가장 큰 순서대로 Hash Bucket이 Temporary Tablespace로 내려가서 구성되며, 디스크로 내려간 Hash Bucket에 변경이 일어날 때마다 디스크 I/O가 발생하게 되어 성능이 현저하게 저하된다. 하드웨어 자원이 넉넉한 상황에서는 다른 조인에 비해 보다 효율적인 수행이 가능하지만, 부족한 상황에서는 다른 조인 방법보다 오히려 느려질 수 있다는 이슈가 발생.
장점 : 하드웨어 자원이 넉넉한 상황에서는 다른 조인에 비해 보다 효율적인 수행이 가능함 단점 : 하드웨어 자원이 부족한 상황에서는 다른 조인 방법보다 비효율적
- 옵티마이저가 Driving Table을 결정함. (Outer Table) - Driving Table이 아닌 테이블은 Drvien Table로 지정함 (Inner Table) - Driving Table의 각 row에 대해 이들이 추출될 때마다 Driven Table의 연관된 모든 row를 조인에 의해 엑세스
* NESTED LOOPS JOIN의 튜닝포인트 ① 테이블 간 조인 횟수를 최소화 할 수 있도록 Driving Table을 구성 -> 조인 순서 제어 ② Driven Table의 연결고리 칼럼에 대한 인덱스 구성 (연결되는 테이블은 인덱스가 있어야 효율적으로 쿼리 실행이 가능함)
* NESTED LOOPS JOIN의 특징 ① 인덱스에 의한 랜덤 엑세스에 기반하고 있기 때문에 대량의 데이터 처리 시 적합하지 않음. (테이블 간의 조인 횟수 최소화를 위한 조인 순서의 최적화) ② Driving Table로는 테이블의 데이터가 적은 마스터 테이블이거나, where절 조건으로 적절하게 row를 제어할 수 있는 것이어야 함 ③ Driven Table에는 조인을 위한 적절한 인덱스가 생성되어 있어야 한다.
2. NESTED LOOPS JOIN의 수행 절차
ex) joinkey_a, joinkey_b, color, size 등은 모두 인덱스임 SELECT /*+ORDERED USE_NL(a b)*/ a.color, ... , b.size, ... FROM table_a a, table_b b WHEREE a.joinkey_a = b.joinkey_b AND a.color = 'RED' AND b.size = 'MED' ; 힌트(a b) 구문을 통해 table a를 먼저 읽고(driving table), USE_NL로 인해 NESTED LOOPS를 사용해 table b를 읽어온다. 연결고리 칼럼 joinkey_b에 인덱스가 존재하므로 효율적으로 쿼리가 실행된다,
3. NESTED LOOPS JOIN 장단점 *장점 ① 인덱스를 통한 랜덤 엑세스(random access) 기반에서 좋은 성능을 보인다. *단점
① 인덱스가 없는 상태에선 속도가 저하됨 ② 대용량 데이터를 처리할 경우 성능이 저하됨.
4. DRVING TABLE의 원리
위 그림과 같이, 여러 번의 NL Join을 진행할 경우, 데이터가 적은 테이블부터 NL Join을 진행하는 것이 효율적.
5. 조인 순서 제어 방법
① 힌트로 아래와 같이 순서를 제어 /*+ORDERED */ -> FROM 절에 기술한 테이블 순서대로 제어 /*+LEADING(table명) */ -> 힌트 내에 제시한 테이블이 Driving Table으로 채택됨 (ORDERED 힌트와 같이 사용할 경우 LEADING 힌트는 무시됨)
② 뷰(view) 활용 ③ suppressing 활용 ④ FROM 절의 테이블 순서 변경 (RBO 에서는 각 테이블에 대한 규칙이 동일할 때, FROM 절로부터 멀리 있는 테이블부터 처리함, CBO에서는 이 방법이 의미가 없다.)
6. 연결고리에 대한 인덱스
① 양 쪽 모두 인덱스가 있는 경우 - 두 테이블 중 조회되는 결과가 적은 테이블을 선택하여 driving table로 선택 ② 한쪽만 인덱스가 있는 경우 - 인덱스가 없는 쪽 테이블을 driving table로 사용함. ③ 양쪽 모두 연결고리에 대한 인덱스가 없는 경우 ex) SELECT * FROM TAB_A A, TAB_B B WHERE A.NAME = B.NAME; A -> B 이던, B -> A이던 Full table scan으로 간다.
driven 테이블에 대한 Full table scan 회수는 driving 테이블로부터 읽어 들이는 row 수만큼 된다.
이러한 상황에서 할 수 있는 조인이 Sort Merge와 Hash조인, Nested Loops 조인방식으로 조인이 이뤄지지 않는다.
- 인덱스로 구성된 칼럼에 변형을 가하게 되면 옵티마이저는 해당 인덱스를 사용하지 못하게 된다. - 많은 양의 데이터를 갖는 테이블인 경우, 전체의 15% 이하를 처리할 때만 인덱스 사용이 빠르다.
1. 인덱스 활용이 불가능한 경우 - 인덱스 스캔이 무조건 빠른건 아니다. - 조건에 의한 처리범위가 넓어짐으로 인해 분포도가 나빠지는 경우가 있는데, 이 경우 인덱스 스캔을 하는 것보다는 FULL TABLE SCAN을 하는 것이 바람직함. -> FULL TABLE SCAN 시엔 한 번의 I/O 때마다 여러 개의 데이터 Blocks을 처리하기 때문에 I/O 횟수가 감소하게 됨.
(인덱스는 한번의 I/O 발생 시, 하나의 Block만 처리가 가능함.)
2. 인덱스 사용이 불가능한 경우
① NOT 연산자 사용 ex) SELECT * FROM emp WHERE empno!= 2170000; SELECT * FROM emp WHERE emp <> 2170000; 한 건의 데이터를 제외하고 나머지 모두를 찾는 것 이므로, FULL TABLE SCAN이 유리하다.
② IS NULL, IS NOT NULL 사용 ex) SELECT * FROM emp WHERE empno is NULL; INDEX에는 NULL 값이 저장되지 않음. 그러므로 NULL 과의 비교연산자는 인덱스를 사용할 수 없다.
③ 옵티마이저의 취사 선택 옵티마이저의 자의적 판단에 의해서 인덱스를 사용할 수도 있고 사용하지 않을 수도 있다. 이러한 것을 '취사선택'이라 한다. Rule Base Optimizer와 Cost Base Optimizer에서는 옵티마이저의 자의적 판단으로 인한 잘못된 선택을 강제로 제어하기 위해 Hint를 사용한다.
④ External suppressing suppressing 이란 인덱스로 구성된 컬럼에 변형을 가했을 때, 그로 인해 해당 컬럼으로 구성된 인덱스를 사용하지 못하는 것을 의미함. External suppressing은 코드와 같이 겉으로 드러나는 suppressing을 의미함.
ex1) 불필요한 함수를 사용하는 경우 WHERE SUBSTR(ename, 1, 1) = 'M' (SUBSTR로 칼럼에 변형을 가함) -> WHERE ename LIKE 'M%'; 로 사용해야 INDEX 사용 가능.
ex2) 문자열 결합 WHERE job || deptno = 'MANAGER10'; (|| 로 칼럼을 변형) -> WHERE job = 'MANAGER' AND deptno = 10; 을 사용해야 인덱스를 사용가능.
ex3) DATE 변수의 가공 WHERE TO_CHAR(hiredate, 'YYYYMMDD') = '20021023' (TO_CHAR로 칼럼이 변형됨) -> WHERE hiredate BETWEEN TO_DATE('20021016' ,'YYYYMMDD') AND TO_DATE('20021023', 'YYYYMMDD') 와 같이 사용하는 게 좋다.
ex4) 산술식의 적용 WHERE sal*12 > 40000; WHERE sal > 40000/12; -> 이렇게 사용
⑤ Internal suppressing Internal suppressing은 DB 내부에서 자체적으로 존재하는 suppressing을 의미함
NUMBER, DATE, CHAR 형을 VARCHAR2로 바꾸거나, 그 반대의 경우들이 있음. 즉 서로 다른 data type끼리 비교와 연산이 발생하는 경우를 의미한다.
3. 옵티마이저에 의한 선택절차
특정 테이블에 대해서 SQL의 주어진 조건으로 인해 사용될 수 있는 인덱스가 두 개 이상인 경우, 옵티마이저는 조건에 가장 적절한 인덱스를 선택하여 사용함. -> 주어진 조건에 가장 적절한 인덱스를 선택하려 할 때, 일련의 절차에 따라 결정한다.
* 옵티마이저의 인덱스 선택 시 판단 절차 ① 주어진 조건에 대한 각 인덱스 별로 매칭률을 계산해서 매칭률이 높은 것을 우선적으로 선택한다. * 인덱스매칭률 = WHERE절에서 1st 칼럼부터 연속된 칼럼에 대해 상수(값)를 '='로 비교하는 칼럼의 개수 / 인덱스를 구성하는 총 칼럼의 개수 ② 인덱스 별 매칭률이 같을 경우에는 인덱스를 구성하는 칼럼의 개수가 많은 것을 우선적으로 선택한다. ③ 인덱브 별 매칭률과 인덱스를 구성하는 칼럼의 개수가 같은 경우에는 가장 최근에 생성된 것을 우선적으로 선택한다.
위 사례에서는 인덱스 변경 전에는 IX1_SALES 인덱스를 타지만, 인덱스 변경 후에는 IX2_SALES 인덱스를 타게된다.
* RBO와 CBO가 선택한 인덱스 차이 ex) EC_COURSE_SQ_PK : COURSE_CODE + YEAR + COURSE_SQ_NO
EC_COURSE_SQ_IDX_01 : YEAR (Non Unique)
SELECT MIN(course_sq_no) AS min_sq
MAX(course_sq_no) AS max_sq
FROM ec_course_sq
WHERE course_code = 1960
AND year = '2002' ;
RBO은 인덱스 매칭률이 높은 것을 선택하므로,
EC_COURSE_SQ_PK 인덱스 매칭률 : (2/3)
EC_COURSE_SQ_IDX_01 : 인덱스 매칭률 (1/1) 에 따라 EC_COURSE_SQ_IDX_01인덱스를 선택함.
CBO는 YEAR 칼럼을 하나 사용하는 것 보다, COURSE_CODE + YEAR 결합 칼럼이 데이터 범위를 줄여줄 수 있다면, EC_COURSE_SQ_PK 인덱스를 선택한다.