ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 웹알못의 데이터 어드민 사이트 만들기(feat. Streamlit) 2. 페이지 작성
    etc. 2022. 9. 10. 01:00

    Streamlit을 띄운 목적이 데이터 애드혹을 줄이는 것이었기 때문에,

    주요 테이블을 조회하는 간단한 쿼리를 누구나 클릭 베이스로 작성할 수 있는 페이지를 가장 먼저 작성해 봤습니다.

     

    부끄럽지만 전체 코드(일부를 제외한) 를 첨부합니다.

    더보기
    import streamlit as st
    import pandas as pd
    from datetime import datetime, timedelta
    
    st.set_page_config(page_title="queryMaker", page_icon="📝", layout='wide')
    st.title("dat-a-dmin [queryMaker]")
    
    ###
    
    d0_options = {
        "유저, 크리에이터": [""],
        "마케팅": [""],
        "사이트/앱 로그": [""],
        "매출": [""],
        "구독": [""],
        "글로벌": [""],
        "클래스, 키트, 스토어": [""],
        "기타": [None]
    }
    
    d0_tu = tuple(d0_options.keys())
    
    depth0 = st.selectbox(
        '어떤 데이터가 필요하신가요?',
        d0_tu   
    )
    
    @st.cache(ttl=10800)
    def get_tables(tablename):
        engine = db.ps_engine()
        schemas = pd.read_sql(f"desc {tablename}", con=engine)
        return schemas
        
    d0_result = (d0_options.get(depth0))
    
    depth1 = st.selectbox(
        label='어떤 테이블을 확인해 볼까요?',
        options=tuple(d0_result),
    )
    
    if depth1==None:
        st.warning("데이터 팀에 문의해 주세요!")
    else:
        sc = get_tables(depth1)
        
    depth2 = st.multiselect(
        '어떤 조건이 필요한가요? (복수 선택)',
        ['날짜 범위', '숫자 범위', '특정 텍스트 포함', 'T/F']
    )
    
    try:
        num_col = sc.loc[(sc['Type'].str.contains('int'))|(sc['Type']=='double')|(sc['Type'].str.contains('decimal')), 'Column']
        date_col = sc.loc[(sc['Type']=='date')|(sc['Type'].str.contains('timestamp')), 'Column']
        text_col = sc.loc[(sc['Type'].str.contains('varchar')), 'Column'] 
        bool_col = sc.loc[(sc['Type']=='boolean'), 'Column'] 
    
        d2_options = {
            "날짜 범위": date_col,
            "숫자 범위": num_col,
            "특정 텍스트 포함": text_col,
            "T/F": bool_col
        }
    
        where_sentence = "where 1=1"
    
        for d2 in depth2:
            globals() ['depth3_'+d2] = st.multiselect(
                f'{d2} 조건 설정할 필드 (복수 선택)',
                d2_options.get(d2)
            )
            
            try:
                for d3 in globals() ['depth3_'+d2]:
                    if d2=='날짜 범위':
                        globals() ['depth4_date_'+d3] = st.date_input(
                            f'{d3} 날짜 범위 선택',
                            [datetime.date(datetime.now() - timedelta(days=30)), datetime.date(datetime.now())]
                        )
                        try:
                            start_date = datetime.strftime(globals() ['depth4_date_'+d3][0], '%Y-%m-%d')
                            end_date = datetime.strftime(globals() ['depth4_date_'+d3][1], '%Y-%m-%d')
                            where_sentence += f"\nand date({d3}) between date'{start_date}' and date'{end_date}'"
                            if 'log' in depth1:
                                start_pymd = datetime.strftime(globals() ['depth4_date_'+d3][0], '%Y%m%d')
                                end_pymd = datetime.strftime(globals() ['depth4_date_'+d3][1], '%Y%m%d') 
                                where_sentence += f"\nand p_ymd between '{start_pymd}' and '{end_pymd}'"
                        except:
                            pass 
    
                    if d2=='숫자 범위':
                        globals() ['depth4_num_'+d3] = st.text_input(
                            f'{d3} 숫자 범위 입력 (x~y)'
                        )
                        try:
                            x = globals() ['depth4_num_'+d3].strip().split('~')[0]
                            y = globals() ['depth4_num_'+d3].strip().split('~')[1]
                            where_sentence += f"\nand {d3} between {x} and {y}"
                        except:
                            pass 
    
                    if d2=='특정 텍스트 포함':
                        globals() ['depth4_text_include_'+d3] = st.text_input(
                            f'{d3} 포함할 텍스트'
                        )
                        globals() ['depth4_text_exclude_'+d3] = st.text_input(
                            f'{d3} 제외할 텍스트'
                        )
                        try:   
                            if globals() ['depth4_text_include_'+d3] != '': 
                                where_sentence += f"\nand {d3} like '%{globals() ['depth4_text_include_'+d3]}%'"
                        except Exception as e:
                            st.write(e)
                            pass
                        try:
                            if globals() ['depth4_text_exclude_'+d3] != '':
                                where_sentence += f"\nand {d3} not like '%{globals() ['depth4_text_exclude_'+d3]}%'"
                        except Exception as e:
                            st.write(e)
                            pass
    
                    if d2=='T/F':
                        globals() ['depth4_bool_'+d3] = st.checkbox(
                            f'{d3} 해당 여부'
                        )
                        try:
                            where_sentence += f"\nand {d3} = {globals() ['depth4_bool_'+d3]}"
                        except:
                            pass
    
                fin_query = f"""select *
    from {depth1}
    {where_sentence}
    limit 10"""
    
            except Exception as e:
                pass
    
        st.markdown('---')
    
        try:
            if fin_query:
                st.subheader("쿼리가 완성되었어요 (복사하세요!)")
                query = st.code(
                    fin_query,
                    language='sql'
                )
            try:
                validation = pd.read_sql(fin_query.replace('%', '%%'), engine).head(5)
                st.write("정상 쿼리입니다.")
                st.markdown("""
                    <head>
                        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
                    </head>
                    <body>
                        <a href="https://redash/queries/new" target = "_blank" class="btn btn-outline-primary" role="button">리대시에 쿼리 붙여넣기</a>
                    </body>
                    """, unsafe_allow_html=True)
                st.info("""
                - 리대시에서 `limit 10`을 지우면 전체 결과를 조회하실 수 있습니다.
                """)
                # st.table(validation)
            except Exception as e:
                st.markdown(f"#### 비정상 쿼리입니다.\n```{e}```")
        except Exception as e:
            pass
    
    except:
        pass

     

    위 코드를 요약하면 다음과 같습니다.

    1. 도메인 선택

    2. 테이블 선택

    3. 쿼리 조건 선택

      - 날짜 범위

      - 숫자 범위

      - 특정 텍스트 포함

      - T/F (bool)

    4. 선택한 조건 입력

    5. 완성된 쿼리 활용

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    구현된 화면은 예상보다 단촐합니다^^;;

     

    요점은, 데이터 마트를 도메인 별로 분류해서 각 도메인의 주요 테이블만 미리 정해 주면

    나머지는 로직에 따라 모든 필드의 where절을 구성할 수 있다는 부분입니다.

    쿼리를 어느 정도 쓰실 줄 아는 분들에게는 별 것 아니지만 where절을 구성하는 것도 어려운 분들께 최소한의 가이드를 잡아드리는 것을 목적으로 했습니다.

     

    물론 이 기능을 사용하기 위해서는 테이블마다 각 필드가 무엇을 의미하는지 정보가 필요한데요,

    저희는 이 부분을 datahub로 보완하고 있습니다.

    https://datahubproject.io/

     

    A Metadata Platform for the Modern Data Stack | DataHub

    DataHub is a data discovery application built on an extensible metadata platform that helps you tame the complexity of diverse data ecosystems.

    datahubproject.io

     

    다음 편에서는 유저 리스트를 추출할 수 있는 서비스와 실제 오픈 후 문제에 대해 회고하겠습니다.

Designed by Tistory.