Web/Flask-python

flask에서 sql을 사용하는 방식

flask에서 sql을 사용하는 방식

크게 두가지 정도 있는듯.

  1. query_engine에 포함시켜 쿼리를 날리고 결과를 받는 방식
  2. 정의된 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에서 찾은 다음, 그 행의 특정 속성값을 가져와 기존 리스트에 추가하는 기능.