flask에서 sql을 사용하는 방식
크게 두가지 정도 있는듯.
- query_engine에 포함시켜 쿼리를 날리고 결과를 받는 방식
- 정의된 model 자체를 받아 사용하는 방식
query_engine 방식
#query_engine.py
def TaskQuery():
sql = "SELECT * FROM task_list"
lresult = db.engine.execute(sql)
return lresult
~~~
#task_view.py
result = query_engine.TaskQuery()
~~~
return render_template('/task/taskOverView.html',data=result)
query_engine에 사용할 쿼리문을 정의하여 sql 쿼리 실행 결과를 받아와 사용하는 방식.
이때 반환되는 데이터의 타입은 'sqlalchemy.engine.cursor.LegacyCursorResult'
랜더할때 데이터를 그대로 인자로 전달해주면 템플릿에서는 딕셔너리나 리스트를 사용하듯이 데이터를 출력해서 사용할 수 있다.
{% for value in data %}
<div class="card TaskCard col-md-2 {{ value[6] }}" id="{{ value[0] }}" >
<div class="TaskMenu dropdown no-arrow">
<div class="fas fa-ellipsis-h TaskDropdown float-right dropdown-toggle"
data-toggle="dropdown" d="TaskChangeSingle" name="TaskChangeSingle" aria-haspopup="true" aria-expanded="false">
</div>
<div class="dropdown-menu" aria-labelledby="TaskChangeSingle">
<div class="dropdown-item" id="selectTaskChangeSingle" value="Complete">완료 처리</div>
<div class="dropdown-item" id="selectTaskChangeSingle" value="Assigned">작업 배정</div>
<div class="dropdown-item" id="selectTaskChangeSingle" value="Unsigned">배정 취소</div>
</div>
<input class="TaskCheckbox float-right" type="checkbox" id="1" style="transform:scale(0.8)"></input>
</div>
<div class="card-body">
<p class="card-title TaskName">{{ value[1] }}</p>
<p class="card-subtitle text-muted TaskInfo">{{ value[2] }}</p>
</div>
<div class="card-footer text-muted">
<div class="row">
<i class="fas fa-calendar-alt col-md-1"></i>
<h6 class="TaskDate col-md-10">{{ value[3][:-7]}}</h6>
<i class="fas fa-user-tag col-md-1"></i>
<h6 class="TaskReq col-md-10">{{ value[4] }}</h6>
<i class="fas fa-wrench col-md-1"></i>
<h6 class="TaskMgr col-md-10">{{ value[5] }}</h6>
<i class="fas fa-hashtag col-md-1"></i>
<h6 class="TaskBinHash col-md-10">{{ value[7] }}</h6>
</div>
</div>
</div>
{% endfor %}
{% for value in data %}
<tr>
<td> {{ value['KST'] }}</td>
<td> {{ value['TypeName'] }}</td>
<td> {{ value['AccountKey'] }}</td>
<td> {{ value['NickName'] }}</td>
</tr>
{% endfor %}
이경우 데이터를 쓰는 방식은 dict나 리스트 등의 일반적인 자료 형태와 동일하지만, 엄밀하게 따지면 다르기 때문에 .append 등을 통한 데이터의 추가나 삭제등이 힘들다.
for s in result :
print(type(s))
print(s)
'sqlalchemy.engine.row.LegacyRow'
(1, '', '', '2021-09-08 12:57:53.914909', '', '미배정', 'Unsigned', 'd41d8cd98f00b204e9800998ecf8427e')
(2, '', '', '2021-09-08 12:59:21.561642', '', '미배정', 'Unsigned', '74399a7fe9df8c6c930b775d66b97759')
데이터 자체를 그대로 사용하기엔 용이하지만, 가공이나 변형을 거쳐 쓰기엔 여러모로 불편한 타입. 쿼리문에서 출력할 데이터 형태를 미리 완성해두고 결과물을 가져와서 출력만 하겠다는 생각으로 쓰기에 적절해보인다.
list방식과 dict 방식 둘 다 사용 가능한 기묘한 형태.
model.query 방식
#models.py
class TaskList(db.Model):
id = db.Column(db.Integer, primary_key=True)
binname = db.Column(db.String(100),nullable=False)
comment = db.Column(db.String(100),nullable=False)
date = db.Column(db.DateTime(),nullable=False)
reqname = db.Column(db.String(100),nullable=False)
mgrname = db.Column(db.String(100),nullable=False)
status = db.Column(db.String(100),nullable=False)
hash = db.Column(db.String(100))
~~
#task_view.py
res1 = TaskList.query.filter(TaskList.mgrname==i.user_id)
dict_result1 = pd.read_sql(res1.statement, res1.session.bind)
정의된 DB 테이블 model을 가져와 필요한 데이터만 필터링하여 pandas의 read_sql() 함수를 가지고 변환해 쓰는 방식. 이 경우는 변환되는 데이터 타입이 pandas.core.frame.DataFrame 이된다.
dataframe 형태로 변환되기 때문에 판다스에서 사용하는 각종 데이터 라이브러리를 사용하기에 용이하다. (다만 지금은 그정도로 큰 데이터셋을 사용하지 않아 체감은 못해봄. )
print(dict_result.loc[0])
~~~
id 1
originid 6
binname README.txt
comment 오진
date 2021-09-08 14:16:03
enddate 2021-09-09 12:06:06.022682
reqname
mgrname synod2
reportpath C:\Users\synod2\Documents\GitHub\WT_Optool_mai...
endstatus 정상 파일
hash 72ac5a8dd6491e525b9783c9bc439fe6
이런식으로 데이터를 보거나 관리하기에는 용이한데, 간편하게 출력해서 보기엔 적합하지 않다. 판다스 데이터프레임 형태이기 때문에 query_engine처럼 템플릿에서 출력하려면 to_html을 쓰거나 딕셔너리나 리스트로의 별도의 변환이 필요하다.
for s in (dict_result.values.tolist()):
print (s)
~~
[1, 6, 'README.txt', '오진', '2021-09-08 14:16:03', Timestamp('2021-09-09 12:06:06.022682'), '', 'synod2', 'C:\\Users\\synod2\\Documents\\GitHub\\WT_Optool_main\\WonderOps\\static\\TaskReports\\sample2jfif.jfif_2021-09-09_120606.022682', '정상 파일', '72ac5a8dd6491e525b9783c9bc439fe6']
[2, 5, 'Everytime 십일절 - 11번가.url', '', '2021-09-08 13:07:58', Timestamp('2021-09-09 12:06:37.401863'), 'aaaa', 'synod2', 'C:\\Users\\synod2\\Documents\\GitHub\\WT_Optool_main\\WonderOps\\static\\TaskReports\\12.txt_2021-09-09_120637.401863', '기타', 'ec43f54b31e22fc56846ae8a1c6dcb78']
tolist() 메소드를 쓰면 list 형태로 바꿔준다. 이 경우는 각 요소를 문자열 형태가 아닌 오브젝트 자체를 반환하는 방식이기 때문에 indices 등을 이용한 정규표현식 사용이 힘들 수 있다.
예를들면, 위 예시의 경우 Timestamp 가 문자열이 아니고 오브젝트 형태로 반환되기 때문에 별도의 변환을 거치지 않으면 문자열 정규표현식 사용이 불가하다.
#task_view.py
~~
qs = TaskList.query.filter(TaskList.mgrname==g.user.user_id)
res = pd.read_sql(qs.statement, qs.session.bind)
result = res.values.tolist()
return render_template('/task/taskOverView.html',data=result)
~~
<p class="card-title TaskName">{{ value[1] }}</p>
<p class="card-subtitle text-muted TaskInfo">{{ value[2] }}</p>
<h6 class="TaskDate col-md-10">{{ ((value[3])|string)[:-7] }}</h6>
만약 위 Timestamp 오브젝트를 템플릿에서 문자열처럼 쓰고싶으면 위와 같이 |string 지시자를 붙여 문자열 형태로 변환해주면 된다. (jinja에서 사용하는 자료형 변환식)
to_dict 메소드를 쓰면 딕셔너리 형태로 바꿔주지만, 이경우는 dict 처럼 쓰지는 못한다.
qs = TaskList.query.filter()
res = pd.read_sql(qs.statement, qs.session.bind)
result = res.values.tolist()
for i,s in enumerate(result) :
result[i].append('1')
이렇게 쓰면 append 등을 통해 원하는 데이터를 자유롭게 붙일 수 있다.
번외. pandas dataframe 에서 특정 데이터 값만 뽑아오기
qs = TaskList.query
res = pd.read_sql(qs.statement, qs.session.bind)
result = res.values.tolist()
cs = CompleteTaskList.query
cres = pd.read_sql(cs.statement, cs.session.bind)
for i,s in enumerate(result) :
if (s[6]) == "Complete" :
tmp = cres[cres['originid'] == s[0]]
result[i].append(tmp['enddate'].values[0])
tasklist에서 6번째 값이 complete인 행을 찾고, 해당 행의 0번째 값인 id와 동일 id값을 가지는 행을 completeTaskList에서 찾은 다음, 그 행의 특정 속성값을 가져와 기존 리스트에 추가하는 기능.