Day1
→ MySQL
⇒ SQL(구조적 절차 언어)
→ DDL(Data Definition Language)
→ DML(Data Manipulation Language)
→ DCL(Data Control Language)
⇒ DBA(DB 전문가)
→ 데이터 아키텍쳐 전문가.(도메인을 알아야함)
CRM(Customer Relationship Management)
요새는 통계학과도 sql 배우는 추세
wsl --shutdown
sudo service mysql status
sudo service mysql start/stop 등 가능
rosie@ming9:~$ mysql -uplay -p000 #임시
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| encore |
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.01 sec)
mysql> use encore;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> show tables;
+------------------+
| Tables_in_encore |
+------------------+
| hotel_ratings |
| members |
| onnuri |
+------------------+
3 rows in set (0.01 sec)
ANSI SQL → 표준의 SQL
IEEE → 세계적인 기술 단체, 인터넷 프로토콜
IEEE 754 paper → 소수점에 대한 정의를 내림.
SELECT *
from hotel_ratings hr
where hr.final_rating in ('5성','4성')
order by final_rating desc, hr.region desc;
SELECT count(*)
from hotel_ratings hr
where hr.final_rating in ('5성','4성')
order by final_rating desc, hr.region desc;
--148
SELECT left(hr.final_rating ,1)
from hotel_ratings hr ;

SELECT concat(substring(hr.final_rating ,1,1),"성급이다")
from hotel_ratings hr ;

SELECT cast(substring(hr.final_rating ,1,1) as signed) as rating
from hotel_ratings hr ;

select * from
(select hotel_name, cast(substring(hr.final_rating ,1,1) as signed) as rating_int
from hotel_ratings hr) hr
where rating_int between 2 and 4;

select region, count(*) cnt
from hotel_ratings
group by region
order by cnt desc;

select region,final_rating, count(*) cnt
from hotel_ratings
group by region,final_rating
order by cnt desc, final_rating;

select region,final_rating, count(*) cnt
from hotel_ratings
group by region,final_rating
having cnt > 30
order by cnt desc, final_rating;

select region, count(*) cnt
from hotel_ratings
group by region
having cnt > 50
order by cnt desc;
CREATE TABLE SK25(
id int PRIMARY key ,
name varchar(10) not null,
phone varchar(30) null,
age tinyint not null,
gender varchar(5));
desc SK25;

구조 보기 위해서 그저 desc SK25; 사용
ALTER TABLE SK25 MODIFY GENDER VARCHAR(5) NOT NULL;
-- 히히 어케 알았지
INSERT INTO SK25 VALUES(1,'김다피','010-0000-0000','20','남성');

update Sk25 s
set age = 40
where id =1;
import requests
from bs4 import BeautifulSoup
import pandas as pd
import io
url = "https://www.koreabaseball.com/Record/Player/HitterDetail/Basic.aspx?playerId=69100"
r = requests.get(url)
pd.read_html(io.StringIO(r.text))[0]
| 팀명 | AVG | G | PA | AB | R | H | 2B | 3B | HR | TB | RBI | SB | CS | SAC | SF |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | LG | 0.286 | 131 | 397 | 343 | 41 | 98 | 16 | 2 | 1 | 121 | 38 | 10 | 5 | 12 |
ALTER TABLE profile ADD bmi float(4,2);
데이터 컬럼 추가
데이터프레임 출력 시 보여주는 것은 그저 뷰에 불과함
그래서 복사해서 사용하지 않으면 일이 복잡해짐
pd.merge(df,tmp2,left_index=True, right_index=True,how='right')
df2.drop(['신장/체중',],axis=1,inplace=True)
확정적으로 수정됨
DBeaver랑 파이썬이 동시에 접근하면 둘 중 하나가 기다리고 있음
conn.close()
trans_dict = {'선수명' : 'NAME',
'등번호' : 'NUMBER',
'생년월일' : 'BIRTH_DAY',
'포지션' : 'POSITION',
'경력' : 'CAREER',
'입단 계약금' : 'SIGNING_BONUS',
'연봉' : 'SALARY',
'지명순위' : 'DRAFT_PICK',
'입단년도' : 'JOIN_YEAR',
'Team' : 'TEAM',
'id' : 'ID'}
df2.rename(columns=trans_dict)
df2.rename(columns=trans_dict,inplace=True)
df2.BIRTH_DAY.apply(lambda x : '-'.join(p.findall(x)))
df2["SALARY"] = df2["SALARY"].apply(
lambda x: int(x.replace("만원", ""))
if x[-2:] == "만원"
else int(x.replace("달러", "")) * 1469
).copy()

ALTER TABLE profile
ADD CONSTRAINT fk_key
FOREIGN KEY (id) REFERENCES pitcher_record (ID)
ON DELETE RESTRICT ON UPDATE CASCADE ;
cursor.execute("truncate table profile")
conn.commit()

import re
p=re.compile('[0-9]+')
"-".join(p.findall('2025년 12월 31일')) # 예시
df.생년월일 = df.생년월일.apply(lambda x: "-".join(p.findall(x)))
df['신장/체중'].str.split('/',expand=True)
pd.concat([df,df['신장/체중'].str.split('/',expand=True)],axis=1)
df.rename(columns={0:'HEIGHT',1:'WEIGHT'},inplace=True)
df.drop(['신장/체중'],axis=1,inplace=True)
df[df.연봉.apply(lambda x : x!='')& df.HEIGHT.apply(lambda x: len(x)>2)]
Day2
SELECT TEAM, AVG(SALARY) AS 평균연봉, AVG(HEIGHT) AS 평균키, AVG(WEIGHT) AS 평균체중, AVG(BMI) AS 평균연봉
FROM profile p
GROUP BY TEAM;
DESC profile;

SELECT *
FROM profile p
WHERE p.SALARY =300000;
SELECT *
FROM profile p
WHERE TEAM LIKE '%SSG%'
ORDER BY p.SALARY DESC;
SELECT NAME , POSITION,
RANK() OVER (PARTITION BY TEAM ORDER BY SALARY DESC) AS SAL_RANK
FROM profile
WHERE TEAM LIKE '%SSG%';

SELECT NAME , POSITION, TEAM,
RANK() OVER (PARTITION BY TEAM ORDER BY SALARY DESC) AS SAL_RANK
FROM profile;
SELECT NAME , POSITION, SALARY, TEAM,
RANK() OVER (ORDER BY SALARY DESC) AS SAL_RANK
FROM profile;

DENSE_RANK OVER
SELECT NAME , POSITION, SALARY, TEAM,
DENSE_RANK() OVER (ORDER BY SALARY DESC) AS SAL_RANK
FROM profile;

ROW_NUMBER
SELECT NAME , POSITION, SALARY, TEAM,
ROW_NUMBER() OVER (ORDER BY SALARY DESC) AS SAL_RANK
FROM profile;

DENSE_RANK OVER PARTITION BY
SELECT NAME , POSITION, SALARY, TEAM,
DENSE_RANK() OVER (PARTITION BY TEAM ORDER BY SALARY DESC) AS SAL_RANK
FROM profile;

SELECT *
FROM
(SELECT NAME , POSITION, SALARY, TEAM,
DENSE_RANK() OVER (PARTITION BY TEAM ORDER BY SALARY DESC) AS SAL_RANK
FROM profile ) AS TEMP

SELECT * 함부로 쓰면 재판을 받을 수 있음
WITH TEMP AS (
SELECT NAME , POSITION, SALARY, TEAM,
DENSE_RANK() OVER (PARTITION BY TEAM ORDER BY SALARY DESC) AS SAL_RANK
FROM profile p
)
SELECT * FROM TEMP
WHERE SAL_RANK =1;

import MySQLdb
conn = MySQLdb.connect(host='myserver', user='play', passwd='123', db='encore')
cursor = conn.cursor()
cursor.execute("SELECT id, NAME, position FROM profile")
rt = cursor.fetchall()
##투수용
pitcher = "https://www.koreabaseball.com/Record/Player/PitcherDetail/Basic.aspx?playerId={}"
##타자용
hitter = "https://www.koreabaseball.com/Record/Player/HitterDetail/Basic.aspx?playerId={}"
import requests
from bs4 import BeautifulSoup
import pandas as pd
import io
for id_,name_ , position_ in rt:
print(id_,name_ , position_)
if position_[:2]=='투수':
r = requests.get(pitcher.format(id_))
else:
r = requests.get(hitter.format(id_))
break
df= pd.concat([pd.read_html(io.StringIO(r.text))[0],pd.read_html(io.StringIO(r.text))[1]],axis=1)
sql = 'INSERT INTO hitter_record VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)'
import numpy as np
cursor.execute(sql, [id_, name_, team_] + df.replace("기록이 없습니다.", None).iloc[0].tolist()[1:])
#df.replace("기록이 없습니다.", np.nan)
conn.commit()
` ← 원래는 테이블 이름에 넣어줘야 했는데 변경됨.
DBA 관심 있는 사람들 경우 InnoDB에 대해 알 필요가 있음.
InnoDB는 mysql의 엔진 중 하나.
import requests
from bs4 import BeautifulSoup
import pandas as pd
import io
import tqdm
##투수용
pitcher = "https://www.koreabaseball.com/Record/Player/PitcherDetail/Basic.aspx?playerId={}"
##타자용
hitter = "https://www.koreabaseball.com/Record/Player/HitterDetail/Basic.aspx?playerId={}"
sql_hitter = 'INSERT INTO hitter VALUES(%s, %s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)'
sql_pitcher = 'INSERT INTO pitcher VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)'
import requests
from bs4 import BeautifulSoup
import pandas as pd
from tqdm import tqdm
for id_, name_, position_, team_ in tqdm(rt):
print(id_, name_, position_, team_)
if position_[:2] == '투수':
r = requests.get(pitcher.format(id_))
sql = sql_pitcher
else:
r = requests.get(hitter.format(id_))
sql = sql_hitter
df = pd.concat([pd.read_html(io.StringIO(r.text))[0], pd.read_html(io.StringIO(r.text))[1]],axis=1 )
try:
cursor.execute(sql, [id_] + df.replace("기록이 없습니다.", None).replace("-", None).iloc[0].tolist()[1:])
except Exception as e:
print(e)
rosie@ming9:~$ mysqldump -uplay -p123 encore > ~/encore.sql
mysql -uplay -p123 encore < ~/encore.sql
~ 과 <~ 는 운영체제 공통 명령어
select *
from profile p
inner join pitcher h
on p.id = h.id
order by h.w desc, h.era;
select p.name, p.SALARY , p.team, h.w, h.era
from profile p
inner join pitcher h
on p.id = h.id
order by h.w desc, h.era;
select p.name, p.SALARY , p.team, p.BIRTH_DAY , p.`POSITION` , h.w
from profile p
LEFT JOIN pitcher h
ON p.id = h.id
with temp as (select p.name, p.SALARY , p.team, p.BIRTH_DAY , p.`POSITION` , h.ops
from profile p
inner JOIN hitter h
ON p.id = h.id)
select team, avg(ops) avg_ops from temp
group by team
order by avg_ops desc;

널 값은 알아서 빼고 연산.
지금 하고 있는 sql 연산들 pd.merge로 연산할 수 있다.
조인은 이너 조인, 아우터 조인, 레프트 조인. 등등이 있다.
create index idx_product_name on onnuri (name);


create view my_kbo as(
select * from profile p where p.salary >100000
)
Day3
pip install fastapi uvicorn
api를 활용할 예정인 것으로 보인다…
from fastapi import FastAPI
app = FastAPI()## 인스턴스 하나 생성
# 데코레이터
@app.get("/encore") #주소
async def get_data(): #비동기
return {'test': 'ㅋㅋㅋㅋ'}
실행메시지 → python -m uvicorn api:app --reload

우선 차이점부터 설명하자면, 동기는 '직렬적'으로 작동하는 방식이고 비동기는 '병렬적'으로 작동하는 방식이다. 즉, 비동기란 특정 코드가 끝날때 까지 코드의 실행을 멈추지 않고 다음 코드를 먼저 실행하는 것을 의미한다. 비동기 처리를 예로 Web API, Ajax, setTimeout 등이 있다.
비동기
GET
Restful API
from fastapi import FastAPI
import MySQLdb
conn = MySQLdb.connect(host='myserver', user='play', passwd='123', db='encore',
autocommit=True)
cursor = conn.cursor()
app = FastAPI()## 인스턴스 하나 생성
# 데코레이터
@app.get("/sk25")
async def get_data(id_): #비동기
sql =f"select * from profile where id = {id_}"
cursor.execute(sql)
rt =cursor.fetchall()
return {'player': rt[0]}


get
post
POST 방식의 요청은 캐시 되지 않습니다.
POST 방식의 요청은 브라우저 히스토리에 남지 않습니다.
POST 방식은 서버의 값이나 상태를 바꾸기 위해 활용
GET은 CRUD 기능 중에 C-Create(생성)의 역할

INFO: 127.0.0.1:57845 - "GET /sk25/?id_=50054 HTTP/1.1" 307 Temporary Redirect
INFO: 127.0.0.1:57845 - "GET /sk25?id_=50054 HTTP/1.1" 200 OK
INFO: 127.0.0.1:53513 - "GET /sk25?id_=50054 HTTP/1.1" 200 OK
@app.get("/sk25") # 데코레이터
async def get_data(id_): #비동기
sql =f"select * from profile where id = {id_}"
cursor.execute(sql)
rt =cursor.fetchall()
if len(rt)==0:
return {'player':'정보없음'}
else:
return {'player': rt[0]}
r =requests.get('http://127.0.0.1:8000/sk25?id_=5004')
r.text
r.json()
total=[]
for x in range(50000,50101):
r =requests.get(f'http://127.0.0.1:8000/sk25?id_={x}').json()['player']
if type(r) ==str:
continue
total.append(r)



Network → Fetch/XHR → name 중 day 선택하거나 filter에 검색
JSON 형식의 데이터를 요청하는 방식으로 JavaScript의 Fetch API와 AJAX 요청을 생성하는 XHR(XMLHttpRequest)를 사용


Status Code
r= requests.get('https://www.melon.com/chart/index.htm')
<Response [406]>
head 없을 때 접근이 안될 가능성 농후

head={
'user-agent':"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"
}
r= requests.get('https://www.melon.com/chart/index.htm',headers= head)
<Response [200]>
from bs4 import BeautifulSoup
bs = BeautifulSoup(r.text)
이쁘게 담아봅시다….

와…이쁘다…

div → 태그
class → 반
id → 본인 이름
bs.find("div", class_ ='service_list_song type02 d_song_list').find_all('tr',class_='lst50')


지역을 바꾸어도 url이 변경되지 않음
SSG
https://databoom.tistory.com/entry/FastAPI-get-post

저거 vscode에 복사해둔 후
ctrl f → & 후 엔터 후 엔터
r= requests.post("https://www.starbucks.co.kr/store/getStore.do?r=N7GEEZLV6B",data=payload)
df=pd.DataFrame(r.json()['list'])
tmp = """<ul class="sido_arae_box"><li><a href="javascript:void(0);" class="set_sido_cd_btn" data-sidocd="01">서울</a></li><li><a href="javascript:void(0);" class="set_sido_cd_btn" data-sidocd="08">경기</a></li><li><a href="javascript:void(0);" class="set_sido_cd_btn" data-sidocd="02">광주</a></li><li><a href="javascript:void(0);" class="set_sido_cd_btn" data-sidocd="03">대구</a></li><li><a href="javascript:void(0);" class="set_sido_cd_btn" data-sidocd="04">대전</a></li><li><a href="javascript:void(0);" class="set_sido_cd_btn" data-sidocd="05">부산</a></li><li><a href="javascript:void(0);" class="set_sido_cd_btn" data-sidocd="06">울산</a></li><li><a href="javascript:void(0);" class="set_sido_cd_btn" data-sidocd="07">인천</a></li><li><a href="javascript:void(0);" class="set_sido_cd_btn" data-sidocd="09">강원</a></li><li><a href="javascript:void(0);" class="set_sido_cd_btn" data-sidocd="10">경남</a></li><li><a href="javascript:void(0);" class="set_sido_cd_btn" data-sidocd="11">경북</a></li><li><a href="javascript:void(0);" class="set_sido_cd_btn" data-sidocd="12">전남</a></li><li><a href="javascript:void(0);" class="set_sido_cd_btn" data-sidocd="13">전북</a></li><li><a href="javascript:void(0);" class="set_sido_cd_btn" data-sidocd="14">충남</a></li><li><a href="javascript:void(0);" class="set_sido_cd_btn" data-sidocd="15">충북</a></li><li><a href="javascript:void(0);" class="set_sido_cd_btn" data-sidocd="16">제주</a></li><li><a href="javascript:void(0);" class="set_sido_cd_btn" data-sidocd="17">세종</a></li></ul>"""
location={x.text : x['data-sidocd'] for x in BeautifulSoup(tmp).find_all('a') }
total=[]
for x in {x.text : x['data-sidocd'] for x in BeautifulSoup(tmp).find_all('a') }.values():
payload['p_sido_cd']=x
r = requests.post('https://www.starbucks.co.kr/store/getStore.do?r=CMS3FGABXL', data=payload)
df=pd.DataFrame((r.json()['list']))
total.append(df)
starbucks_df=pd.concat(total,ignore_index=True)
from urllib import request
import os
if not os.path.isdir("./star"):
os.mkdir("./star")
for idx, x in starbucks_df[['s_name','defaultimage']].iterrows():
if '서초' in x.s_name :
try:
# print(f'{x.s_name} -> https://www.starbucks.co.kr{x['defaultimage']}')
request.urlretrieve(f"https://www.starbucks.co.kr{x['defaultimage']}", f"./star/{x.s_name}.jpg")
except:
pass
url = "https://www.mega-mgccoffee.com/store/find/store.php?sigungu=%EA%B0%95%EB%82%A8%EA%B5%AC"
from urllib import parse
parse.unquote(url)
mega_url ="https://www.mega-mgccoffee.com/store/find/store.php?"
maga_payload={'sigungu':"서초구"}
requests.post(mega_url,data =maga_payload).json()['positions']
Day4
head = {'user-agent' :
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36'}
p = re.compile('value="([0-9a-zA-Z-]+)"')
with requests.session() as s :
r = s.get('https://gs25.gsretail.com/gscvs/ko/store-services/locations#;')
tmp = BeautifulSoup(r.text).find('form', id='CSRFForm')
# print(p.findall(str(tmp)))
## 보안 이슈를 해결하기 위해 csrf 토큰 정보를 삽입하여 url에 접속한다
csrf = p.findall(str(tmp))[0]
url = "https://gs25.gsretail.com/gscvs/ko/store-services/locationList?CSRFToken={}".format(csrf)
r2 = s.post(url, data=payload, headers=head)
print(r2.status_code)
print(r2.json())

부산시 클릭할 때 관련 정보가 오른쪽 바가 변경되어야함
for x in data.find_all('option')[1:]:
print(x['value'],x.text)

list(set([y for x in gs25_service.offeringService.dropna() for y in x]))
pd.merge(gs25_service,pd.DataFrame(columns=service_list),left_index=True,right_index=True,how="left")
gs_melt=pd.melt(gs25_service2,id_vars=['shopCode'])
gs_melt.pivot(index='shopCode',columns='variable',values='value').reset_index()
import MySQLdb
conn = MySQLdb.connect(host='myserver', user='play', passwd='123', db='encore', autocommit = True)
cursor = conn.cursor()
import sqlalchemy
from urllib import parse
user = 'play'
password = '123'
host='myserver'
port = 3306
database = 'encore'
password = parse.quote_plus(password)
engine = sqlalchemy.create_engine(f"mysql://{user}:{password}@{host}:{port}/{database}")
try:
with engine.connect() as connection:
print("Database connection successful!")
except Exception as e:
print(f"Database connection failed: {e}")
gs25.to_sql('gs25', index=False , if_exists='replace', con=engine)
select gs.shopCode, g.shopName , g.address ,gs.variable
from gs25_service gs
inner join gs25 g
on gs.shopCode = g.shopCode
where g.address like "%서초%" and gs.variable ='parcel_service';
세션 정보를 받아와서 접속하면 속음
로그인 정보를 세션한테 넘겨주면 빠르다