├── LICENSE ├── README.md ├── data.csv ├── resources ├── Snowflake Table Catalog.png └── Snowflake-Table-Catalog.png ├── sample_data.csv ├── snowflake-table-catalog-offline.py ├── snowflake-table-catalog.py └── style.css /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Mustafa Aydogdu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Snowflake Table Catalog with Streamlit 2 | This is a streamlit demo application that visualizes the table metadata from SNOWFLAKE database and helps exploring tables. 3 | 4 | Snowflake provides a system-defined, read-only shared database named SNOWFLAKE that contains metadata, as well as historical usage data, about the objects in your organization and accounts. This database provides information about your tables as well. 5 | 6 | Whenever you want to understand your tables, you can query these tables directly and get the required information. On the other hand, you can use streamlit to create applications to monitor usage or metadata from Snowflake. 7 | 8 | Enjoy this online demo application hosted in Streamlit cloud with demo data. 9 | https://snow-table-catalog.streamlit.app/ 10 | 11 | 12 | 13 | ![This is an image](resources/Snowflake-Table-Catalog.png) 14 | 15 | 16 | 17 | ## Prepare Your Application 18 | 19 | Set up credentials. Create a file .streamlit/secrets.toml and fill in your Snowflake account credentials. The file should look like this: 20 | 21 | ``` 22 | [snowflake] 23 | user = "..." 24 | password = "..." 25 | account = "..." 26 | warehouse = "..." 27 | role = "..." 28 | ``` 29 | 30 | Role should have access to Snowflake Account_Usage Database and Schema. 31 | 32 | 33 | ## Run Your Application 34 | ``` 35 | streamlit run snowflake-table-catalog.py 36 | 37 | ``` 38 | or 39 | 40 | ``` 41 | python -m streamlit run snowflake-table-catalog.py 42 | 43 | ``` 44 | 45 | Edit: Application was tested with Streamlit 1.11.1 and Snowflake Python Connector 2.7.7. 46 | -------------------------------------------------------------------------------- /data.csv: -------------------------------------------------------------------------------- 1 | TABLE_ID,TABLE_CATALOG,CREATED,TABLE_NAME,TABLE_SCHEMA,TABLE_OWNER,TABLE_TYPE,IS_TRANSIENT,CLUSTERING_KEY,ROW_COUNT,BYTES,RETENTION_TIME,LAST_ALTERED,AUTO_CLUSTERING_ON,COMMENT,COLUMN_COUNT 2 | 50184,FROSTBYTE_SUPERBANK,2022-06-16T00:11:10.029-07:00,CREDIT_TRENDS,EQUIFAX_RAW_,SYSADMIN,BASE TABLE,NO,,53,4096,1,2022-06-16T00:11:10.849-07:00,NO,,8 3 | 87043,CITIBIKE_DEV,2023-01-23T01:48:37-08:00,SPATIAL_DATA,UTILS,DEV_CITIBIKE,BASE TABLE,NO,,2112,679936,1,2023-01-23T01:48:40.134-08:00,NO,,2 4 | 6160,CITIBIKE_V4_RESET,2022-01-21T03:12:02.121-08:00,WEIGHT_BIRTHYEAR,RESET,DBA_CITIBIKE,BASE TABLE,NO,,65,2560,1,2022-01-21T03:12:09.223-08:00,NO,,3 5 | 7182,CITIBIKE_V4_RESET,2022-01-21T03:12:05.739-08:00,WEIGHT_HOD,RESET,DBA_CITIBIKE,BASE TABLE,NO,,168,3584,1,2022-01-21T03:12:18.227-08:00,NO,,4 6 | 7174,CITIBIKE_V4_RESET,2022-01-21T03:12:02.499-08:00,WEIGHT_GENDER,RESET,DBA_CITIBIKE,BASE TABLE,NO,,3,1536,1,2022-01-21T03:12:11.096-08:00,NO,,3 7 | 6164,CITIBIKE_V4_RESET,2022-01-21T03:12:05.094-08:00,WEIGHT_DOW,RESET,DBA_CITIBIKE,BASE TABLE,NO,,7,1536,1,2022-01-21T03:12:17.1-08:00,NO,,3 8 | 6162,CITIBIKE_V4_RESET,2022-01-21T03:12:04.071-08:00,WEIGHT_ROUTE,RESET,DBA_CITIBIKE,BASE TABLE,NO,,200,6144,1,2022-01-21T03:12:14.61-08:00,NO,,5 9 | 87041,CITIBIKE_DEV,2023-01-23T01:48:37.336-08:00,TRIPS,DEMO,DBA_CITIBIKE,BASE TABLE,NO,,6210016,200996864,1,2023-01-23T01:48:37.494-08:00,NO,,3 10 | 7186,CITIBIKE_V4_RESET,2022-01-21T03:12:21.552-08:00,TRIPS_FULL,RESET,DBA_CITIBIKE,BASE TABLE,YES,,49836757,2594766848,1,2022-01-21T03:12:35.771-08:00,NO,,18 11 | 59394,FROSTBYTE_SUPERBANK,2022-08-02T05:00:36.094-07:00,LOAN_FACT,RAW_,ACCOUNTADMIN,BASE TABLE,NO,,215000,6774784,1,2023-01-25T00:39:58.986-08:00,NO,,29 12 | 7184,CITIBIKE_V4_RESET,2022-01-21T03:12:06.222-08:00,RIDERS,RESET,DBA_CITIBIKE,BASE TABLE,NO,,100000,6100992,1,2022-01-21T03:12:20.605-08:00,NO,,11 13 | 7180,CITIBIKE_V4_RESET,2022-01-21T03:12:04.59-08:00,WEIGHT_WK,RESET,DBA_CITIBIKE,BASE TABLE,NO,,519,11776,1,2022-01-21T03:12:15.628-08:00,NO,,3 14 | 57350,FROSTBYTE_SUPERBANK,2022-08-01T23:04:17.661-07:00,ANALYST_TO_LOAN_TYPE_MAP,ANALYTICS,DATA_ENGINEER_SB,BASE TABLE,NO,,2,1536,1,2022-08-01T23:04:24.661-07:00,NO,,2 15 | 52228,FROSTBYTE_SUPERBANK,2022-06-16T00:11:02.888-07:00,LOAN_ACCOUNT_DIM,RAW_,SYSADMIN,BASE TABLE,NO,,100000,3018752,1,2022-06-16T00:11:06.17-07:00,NO,,33 16 | 84994,CITIBIKE,2023-01-23T01:11:05.508-08:00,TRIPS,DEMO,DBA_CITIBIKE,BASE TABLE,NO,,40721893,1328151552,1,2023-01-24T02:33:54.806-08:00,NO,,3 17 | 74761,CITIBIKE,2022-11-29T02:15:02.154-08:00,SPATIAL_DATA,UTILS,DEV_CITIBIKE,BASE TABLE,NO,,2112,679936,1,2022-11-29T02:15:07.097-08:00,NO,,2 18 | 90116,DEMO_DATA,2023-02-22T09:58:25.207-08:00,T1,PUBLIC,ACCOUNTADMIN,BASE TABLE,NO,,2,1024,1,2023-02-22T10:46:44.167-08:00,NO,,1 19 | 89090,FROSTBYTE_SUPERBANK,2023-01-25T00:40:17.044-08:00,LOAN_FACT_CL,RAW_,ACCOUNTADMIN,BASE TABLE,NO,,215000,6774784,1,2023-01-25T00:40:17.689-08:00,NO,,29 20 | 51204,FROSTBYTE_SUPERBANK,2022-06-16T00:11:08.493-07:00,TERRITORY_DIM,RAW_,SYSADMIN,BASE TABLE,NO,,53,1536,1,2022-06-16T00:11:09.669-07:00,NO,,2 21 | 7176,CITIBIKE_V4_RESET,2022-01-21T03:12:03.06-08:00,WEIGHT_PAYMENT,RESET,DBA_CITIBIKE,BASE TABLE,NO,,7,1536,1,2022-01-21T03:12:12.47-08:00,NO,,3 22 | 7178,CITIBIKE_V4_RESET,2022-01-21T03:12:03.561-08:00,WEIGHT_MEMBERSHIP,RESET,DBA_CITIBIKE,BASE TABLE,NO,,7,1536,1,2022-01-21T03:12:13.529-08:00,NO,,3 23 | 90120,DEMO_DATA,2023-02-22T10:46:35.095-08:00,T2,PUBLIC,ACCOUNTADMIN,BASE TABLE,NO,,2,1024,1,2023-02-22T10:46:36.042-08:00,NO,,1 24 | 65538,FROSTBYTE_SUPERBANK,2022-10-09T22:56:41.785-07:00,ACCOUNT_DETAIL_V,ANALYTICS,DATA_ENGINEER_SB,VIEW,NO,,0,0,,2022-10-09T22:56:42.074-07:00,NO,,6 25 | 50186,FROSTBYTE_SUPERBANK,2022-06-16T00:11:12.919-07:00,LOAN_MARKETSHARE_BI_V,ANALYTICS,SYSADMIN,VIEW,NO,,0,0,,2022-06-16T00:11:13.327-07:00,NO,,16 26 | 87044,CITIBIKE_DEV,2023-01-23T01:48:37.336-08:00,TABLEAU_QUERY_HISTORY,UTILS,DBA_CITIBIKE,VIEW,NO,,0,0,,2023-01-23T01:48:40.645-08:00,NO,,42 27 | 52234,FROSTBYTE_SUPERBANK,2022-06-16T00:11:12.24-07:00,LOAN_BY_ZIPCODE_DAILY_V,HARMONIZED,SYSADMIN,VIEW,NO,,0,0,,2022-06-16T00:11:12.638-07:00,NO,,16 28 | 88066,CITIBIKE,2023-01-24T02:33:56.462-08:00,TRIPS_VW,DEMO,DBA_CITIBIKE,VIEW,NO,,0,0,,2023-01-24T02:33:56.847-08:00,NO,,16 29 | 52230,FROSTBYTE_SUPERBANK,2022-06-16T00:11:11.176-07:00,LOAN_DETAIL_V,HARMONIZED,SYSADMIN,VIEW,NO,,0,0,,2022-06-16T00:11:11.449-07:00,NO,,38 30 | 87042,CITIBIKE_DEV,2023-01-23T01:48:37.336-08:00,TRIPS_VW,DEMO,DBA_CITIBIKE,VIEW,NO,,0,0,,2023-01-23T01:48:38.732-08:00,NO,,16 31 | 50188,FROSTBYTE_SUPERBANK,2022-06-16T00:11:13.601-07:00,LOAN_MARKETSHARE_V,ANALYTICS,SYSADMIN,VIEW,NO,,0,0,,2022-06-16T00:11:13.857-07:00,NO,,16 32 | 52232,FROSTBYTE_SUPERBANK,2022-06-16T00:11:11.763-07:00,LOAN_BY_ZIPCODE_V,HARMONIZED,SYSADMIN,VIEW,NO,,0,0,,2022-06-16T00:11:11.962-07:00,NO,,8 33 | 52236,FROSTBYTE_SUPERBANK,2022-06-16T00:11:14.182-07:00,LOAN_DETAIL_V,ANALYTICS,SYSADMIN,VIEW,NO,,0,0,,2022-10-09T22:57:16.686-07:00,NO,,38 34 | 74762,CITIBIKE,2022-11-29T02:15:02.154-08:00,TABLEAU_QUERY_HISTORY,UTILS,DBA_CITIBIKE,VIEW,NO,,0,0,,2022-11-29T02:15:07.647-08:00,NO,,42 35 | 7188,CITIBIKE_V4_RESET,2022-01-21T03:12:37.863-08:00,TRIPS,RESET,DBA_CITIBIKE,VIEW,NO,,0,0,,2022-01-21T03:12:38.072-08:00,NO,,18 36 | -------------------------------------------------------------------------------- /resources/Snowflake Table Catalog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mydgd/snowflake-table-catalog/1f0ab19a4fbada25b2bec0218b57b6c04bc86128/resources/Snowflake Table Catalog.png -------------------------------------------------------------------------------- /resources/Snowflake-Table-Catalog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mydgd/snowflake-table-catalog/1f0ab19a4fbada25b2bec0218b57b6c04bc86128/resources/Snowflake-Table-Catalog.png -------------------------------------------------------------------------------- /sample_data.csv: -------------------------------------------------------------------------------- 1 | TABLE_ID,TABLE_CATALOG,CREATED,TABLE_NAME,TABLE_SCHEMA,TABLE_OWNER,TABLE_TYPE,IS_TRANSIENT,CLUSTERING_KEY,ROW_COUNT,BYTES,RETENTION_TIME,LAST_ALTERED,AUTO_CLUSTERING_ON,COMMENT,COLUMN_COUNT 2 | 87043,CITIBIKE_DEV,2023-01-23T01:48:37.336-08:00,SPATIAL_DATA,UTILS,DEV_CITIBIKE,BASE TABLE,NO,,2112,679936,1,2023-01-23T01:48:40.134-08:00,NO,,2 3 | 6162,CITIBIKE_V4_RESET,2022-01-21T03:12:04.071-08:00,WEIGHT_ROUTE,RESET,DBA_CITIBIKE,BASE TABLE,NO,,200,6144,1,2022-01-21T03:12:14.61-08:00,NO,,5 4 | 6164,CITIBIKE_V4_RESET,2022-01-21T03:12:05.094-08:00,WEIGHT_DOW,RESET,DBA_CITIBIKE,BASE TABLE,NO,,7,1536,1,2022-01-21T03:12:17.1-08:00,NO,,3 5 | 7182,CITIBIKE_V4_RESET,2022-01-21T03:12:05.739-08:00,WEIGHT_HOD,RESET,DBA_CITIBIKE,BASE TABLE,NO,,168,3584,1,2022-01-21T03:12:18.227-08:00,NO,,4 6 | 7174,CITIBIKE_V4_RESET,2022-01-21T03:12:02.499-08:00,WEIGHT_GENDER,RESET,DBA_CITIBIKE,BASE TABLE,NO,,3,1536,1,2022-01-21T03:12:11.096-08:00,NO,,3 7 | 87041,CITIBIKE_DEV,2023-01-23T01:48:37.336-08:00,TRIPS,DEMO,DBA_CITIBIKE,BASE TABLE,NO,,6210016,200996864,1,2023-01-23T01:48:37.494-08:00,NO,,3 8 | 7186,CITIBIKE_V4_RESET,2022-01-21T03:12:21.552-08:00,TRIPS_FULL,RESET,DBA_CITIBIKE,BASE TABLE,YES,,49836757,2594766848,1,2022-01-21T03:12:35.771-08:00,NO,,18 9 | 7184,CITIBIKE_V4_RESET,2022-01-21T03:12:06.222-08:00,RIDERS,RESET,DBA_CITIBIKE,BASE TABLE,NO,,100000,6100992,1,2022-01-21T03:12:20.605-08:00,NO,,11 10 | 7180,CITIBIKE_V4_RESET,2022-01-21T03:12:04.59-08:00,WEIGHT_WK,RESET,DBA_CITIBIKE,BASE TABLE,NO,,519,11776,1,2022-01-21T03:12:15.628-08:00,NO,,3 11 | 84994,CITIBIKE,2023-01-23T01:11:05.508-08:00,TRIPS,DEMO,DBA_CITIBIKE,BASE TABLE,NO,,40721893,1328151552,1,2023-01-24T02:33:54.806-08:00,NO,,3 12 | 74761,CITIBIKE,2022-11-29T02:15:02.154-08:00,SPATIAL_DATA,UTILS,DEV_CITIBIKE,BASE TABLE,NO,,2112,679936,1,2022-11-29T02:15:07.097-08:00,NO,,2 13 | 7176,CITIBIKE_V4_RESET,2022-01-21T03:12:03.06-08:00,WEIGHT_PAYMENT,RESET,DBA_CITIBIKE,BASE TABLE,NO,,7,1536,1,2022-01-21T03:12:12.47-08:00,NO,,3 14 | 7178,CITIBIKE_V4_RESET,2022-01-21T03:12:03.561-08:00,WEIGHT_MEMBERSHIP,RESET,DBA_CITIBIKE,BASE TABLE,NO,,7,1536,1,2022-01-21T03:12:13.529-08:00,NO,,3 15 | 74762,CITIBIKE,2022-11-29T02:15:02.154-08:00,TABLEAU_QUERY_HISTORY,UTILS,DBA_CITIBIKE,VIEW,NO,,0,0,,2022-11-29T02:15:07.647-08:00,NO,,42 16 | 87042,CITIBIKE_DEV,2023-01-23T01:48:37.336-08:00,TRIPS_VW,DEMO,DBA_CITIBIKE,VIEW,NO,,0,0,,2023-01-23T01:48:38.732-08:00,NO,,16 17 | 87044,CITIBIKE_DEV,2023-01-23T01:48:37.336-08:00,TABLEAU_QUERY_HISTORY,UTILS,DBA_CITIBIKE,VIEW,NO,,0,0,,2023-01-23T01:48:40.645-08:00,NO,,42 18 | 7188,CITIBIKE_V4_RESET,2022-01-21T03:12:37.863-08:00,TRIPS,RESET,DBA_CITIBIKE,VIEW,NO,,0,0,,2022-01-21T03:12:38.072-08:00,NO,,18 19 | 88066,CITIBIKE,2023-01-24T02:33:56.462-08:00,TRIPS_VW,DEMO,DBA_CITIBIKE,VIEW,NO,,0,0,,2023-01-24T02:33:56.847-08:00,NO,,16 20 | 6160,CITIBIKE_V4_RESET,2022-01-21T03:12:02.124-08:00,WEIGHT_BIRTHYEAR,RESET,DBA_CITIBIKE,BASE TABLE,NO,,65,2560,1,2022-01-21T03:12:09.223-08:00,NO,,3 21 | -------------------------------------------------------------------------------- /snowflake-table-catalog-offline.py: -------------------------------------------------------------------------------- 1 | #from turtle import onclick 2 | import streamlit as st 3 | import pandas as pd 4 | import io 5 | import requests 6 | #import streamlit.components.v1 as components 7 | st.set_page_config(layout="wide") 8 | # Initialize connection. 9 | # Uses st.experimental_singleton to only run once. 10 | 11 | url="https://raw.githubusercontent.com/mydgd/snowflake-table-catalog/main/sample_data.csv" 12 | s=requests.get(url).content 13 | df=pd.read_csv(io.StringIO(s.decode('utf-8'))) 14 | 15 | df['CREATED'] = pd.to_datetime(df['CREATED']) 16 | df['LAST_ALTERED'] = pd.to_datetime(df['LAST_ALTERED']) 17 | 18 | df2 = df 19 | # if 'df' not in st.session_state: 20 | # st.session_state.df = df 21 | 22 | st.title('Snowflake Table Catalog') 23 | 24 | def human_bytes(B): 25 | """Return the given bytes as a human friendly KB, MB, GB, or TB string.""" 26 | B = float(B) 27 | KB = float(1024) 28 | MB = float(KB ** 2) # 1,048,576 29 | GB = float(KB ** 3) # 1,073,741,824 30 | TB = float(KB ** 4) # 1,099,511,627,776 31 | 32 | if B < KB: 33 | return '{0} {1}'.format(B, '') 34 | elif KB <= B < MB: 35 | return '{0:.2f}'.format(B / KB) 36 | elif MB <= B < GB: 37 | return '{0:.2f}'.format(B / MB) 38 | elif GB <= B < TB: 39 | return '{0:.2f}'.format(B / GB) 40 | elif TB <= B: 41 | return '{0:.2f}'.format(B / TB) 42 | 43 | 44 | def human_bytes_text(B): 45 | """Return the given bytes as a human friendly KB, MB, GB, or TB string.""" 46 | B = float(B) 47 | KB = float(1024) 48 | MB = float(KB ** 2) # 1,048,576 49 | GB = float(KB ** 3) # 1,073,741,824 50 | TB = float(KB ** 4) # 1,099,511,627,776 51 | 52 | if B < KB: 53 | return 'Bytes' 54 | elif KB <= B < MB: 55 | return 'KB' 56 | elif MB <= B < GB: 57 | return 'MB' 58 | elif GB <= B < TB: 59 | return 'GB' 60 | elif TB <= B: 61 | return 'TB' 62 | 63 | 64 | def human_format(num): 65 | magnitude = 0 66 | while abs(num) >= 1000: 67 | magnitude += 1 68 | num /= 1000.0 69 | # add more suffixes if you need them 70 | return ('%.2f%s' % (num, ['', 'K', 'M', 'G', 'T', 'P'][magnitude])).replace('.00', '') 71 | 72 | 73 | def local_css(file_name): 74 | with open(file_name) as f: 75 | st.markdown(f'', unsafe_allow_html=True) 76 | 77 | 78 | def remote_css(url): 79 | st.markdown(f'', 80 | unsafe_allow_html=True) 81 | 82 | 83 | def header_bg(table_type): 84 | if table_type == "BASE TABLE": 85 | return "tablebackground" 86 | elif table_type == "VIEW": 87 | return "viewbackground" 88 | else: 89 | return "mvbackground" 90 | 91 | 92 | remote_css( 93 | "https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css") 94 | 95 | 96 | local_css("style.css") 97 | cb_view_details = st.sidebar.checkbox('View Details') 98 | 99 | if cb_view_details: 100 | view_details="" 101 | else: 102 | view_details="""style="display: none;" """ 103 | 104 | selectbox_orderby = st.sidebar.selectbox("Order By", ('A → Z', 'Z → A', 'Data Size ↓', 'Data Size ↑', 105 | 'Rows ↓', 'Rows ↑', 'Date Created ↓', 'Date Created ↑', 'Date Altered ↓', 'Date Altered ↑')) 106 | #button_clicked = st.button("OK") 107 | 108 | all_option = pd.Series(['All'], index=[9999999]) 109 | 110 | #TABLE_SCHEMA=TABLE_SCHEMA.append({'TABLE_SCHEMA':'All'},ignore_index = True) 111 | 112 | if 'selectbox_database_key' not in st.session_state: 113 | st.session_state.selectbox_database_key = 10 114 | st.session_state.selectbox_schema_key = 20 115 | st.session_state.selectbox_owner_key = 30 116 | st.session_state.selectbox_table_type_key = 40 117 | st.session_state.selectbox_max_rows_key = 50 118 | st.session_state.selectbox_data_size_key = 60 119 | 120 | # Table Catalog/Database 121 | fv_database = df['TABLE_CATALOG'].drop_duplicates() 122 | fv_database = pd.concat([fv_database,all_option]) 123 | 124 | selectbox_database = st.sidebar.selectbox( 125 | 'Database', fv_database, index=len(fv_database)-1, key=st.session_state.selectbox_database_key) 126 | 127 | if selectbox_database != 'All': 128 | df = df.loc[df['TABLE_CATALOG'] == selectbox_database] 129 | else: 130 | df = df.loc[df['TABLE_CATALOG'].isin(fv_database)] 131 | 132 | # Table Schema 133 | fv_table_schema = df['TABLE_SCHEMA'].drop_duplicates() 134 | fv_table_schema = pd.concat([fv_table_schema, all_option]) 135 | 136 | selectbox_schema = st.sidebar.selectbox( 137 | "Table Schema", fv_table_schema, len(fv_table_schema)-1, key=st.session_state.selectbox_schema_key) 138 | 139 | if selectbox_schema != 'All': 140 | df = df.loc[df['TABLE_SCHEMA'] == selectbox_schema] 141 | else: 142 | df = df.loc[df['TABLE_SCHEMA'].isin(fv_table_schema)] 143 | 144 | # Table Owner 145 | fv_owner = df['TABLE_OWNER'].drop_duplicates() 146 | fv_owner = pd.concat([fv_owner,all_option]) 147 | selectbox_owner = st.sidebar.selectbox( 148 | "Table Owner", fv_owner, len(fv_owner)-1, key=st.session_state.selectbox_owner_key) 149 | 150 | if selectbox_owner != 'All': 151 | df = df.loc[df['TABLE_OWNER'] == selectbox_owner] 152 | else: 153 | df = df.loc[df['TABLE_OWNER'].isin(fv_owner)] 154 | 155 | # Table Type 156 | fv_table_type = df['TABLE_TYPE'].drop_duplicates() 157 | selectbox_table_type = st.sidebar.multiselect( 158 | 'Table Type', fv_table_type, fv_table_type, key=st.session_state.selectbox_table_type_key) 159 | 160 | if len(selectbox_table_type) > 0: 161 | df = df.loc[df['TABLE_TYPE'].isin(selectbox_table_type)] 162 | else: 163 | df = df.loc[df['TABLE_TYPE'].isin(fv_table_type)] 164 | 165 | # #!!! This part is disabled since sliders are causing performance issues with large datasets.!!! 166 | # # data size selection 167 | max_data_mb = int(df['BYTES'].max()/1048576) 168 | step_size = 1 169 | 170 | if max_data_mb>1000: 171 | step_size=10 172 | elif max_data_mb>1000000: 173 | step_size=100 174 | elif max_data_mb>1000000000: 175 | step_size=1000 176 | elif max_data_mb>1000000000000: 177 | step_size=10000 178 | 179 | data_size = st.sidebar.slider( 180 | 'Data Size (MB)', 0, max_data_mb+1, (0, max_data_mb+1), key=st.session_state.selectbox_data_size_key, step=step_size) 181 | df = df.loc[(df['BYTES'] >= data_size[0]*1048576) & 182 | (df['BYTES'] <= data_size[1]*1048576)] 183 | 184 | # rows selection 185 | max_rows = int(df['ROW_COUNT'].max()) 186 | step_size = 10 187 | 188 | if max_rows>1000000: 189 | step_size=100 190 | elif max_rows>1000000000: 191 | step_size=1000 192 | elif max_rows>1000000000000: 193 | step_size=10000 194 | 195 | data_rows = st.sidebar.slider('Number of Rows', 0, max_rows+1, 196 | (0, max_rows+1), key=st.session_state.selectbox_max_rows_key, step=step_size) 197 | df = df.loc[(df['ROW_COUNT'] >= data_rows[0]) & 198 | (df['ROW_COUNT'] <= data_rows[1])] 199 | 200 | 201 | def reset_button(): 202 | st.session_state.selectbox_database_key = st.session_state.selectbox_database_key+1 203 | st.session_state.selectbox_schema_key = st.session_state.selectbox_schema_key+1 204 | st.session_state.selectbox_owner_key = st.session_state.selectbox_owner_key+1 205 | st.session_state.selectbox_table_type_key = st.session_state.selectbox_table_type_key+1 206 | st.session_state.selectbox_max_rows_key = st.session_state.selectbox_max_rows_key+1 207 | st.session_state.selectbox_data_size_key = st.session_state.selectbox_data_size_key+1 208 | 209 | 210 | clear_button = st.sidebar.button( 211 | label='Clear Selections', on_click=reset_button) 212 | 213 | if clear_button: 214 | df = df2 215 | 216 | # Card order 217 | orderby_column = '' 218 | orderby_asc = True 219 | 220 | 221 | if selectbox_orderby == 'A → Z': 222 | orderby_column = 'TABLE_NAME' 223 | orderby_asc = True 224 | elif selectbox_orderby == 'Z → A': 225 | orderby_column = 'TABLE_NAME' 226 | orderby_asc = False 227 | elif selectbox_orderby == 'Data Size ↓': 228 | orderby_column = 'BYTES' 229 | orderby_asc = False 230 | elif selectbox_orderby == 'Data Size ↑': 231 | orderby_column = 'BYTES' 232 | orderby_asc = True 233 | elif selectbox_orderby == 'Rows ↓': 234 | orderby_column = 'ROW_COUNT' 235 | orderby_asc = False 236 | elif selectbox_orderby == 'Rows ↑': 237 | orderby_column = 'ROW_COUNT' 238 | orderby_asc = True 239 | elif selectbox_orderby == 'Date Created ↓': 240 | orderby_column = 'CREATED' 241 | orderby_asc = False 242 | elif selectbox_orderby == 'Date Created ↑': 243 | orderby_column = 'CREATED' 244 | orderby_asc = True 245 | elif selectbox_orderby == 'Date Altered ↓': 246 | orderby_column = 'LAST_ALTERED' 247 | orderby_asc = False 248 | elif selectbox_orderby == 'Date Altered ↑': 249 | orderby_column = 'LAST_ALTERED' 250 | orderby_asc = True 251 | 252 | 253 | df.sort_values(by=[orderby_column], inplace=True, ascending=orderby_asc) 254 | 255 | 256 | 257 | table_scorecard = """ 258 |
259 |
260 |
"""+str(df[df['TABLE_TYPE'] == 'BASE TABLE']['TABLE_ID'].count())+""" 261 |
262 |
263 | Tables 264 |
265 |
266 |
267 |
"""+str(df[df['TABLE_TYPE'] == 'VIEW']['TABLE_ID'].count())+""" 268 |
269 |
270 | Views 271 |
272 |
273 |
274 |
"""+str(df[df['TABLE_TYPE'] == 'MATERIALIZED VIEW']['TABLE_ID'].count())+""" 275 |
276 |
277 | Materialized Views 278 |
279 |
280 |
281 |
282 | """+human_format(df['ROW_COUNT'].sum())+""" 283 |
284 |
285 | Rows 286 |
287 |
288 | 289 |
290 |
291 | """+human_bytes(df['BYTES'].sum())+" "+human_bytes_text(df['BYTES'].sum())+""" 292 |
293 |
294 | Data Size 295 |
296 |
297 |
""" 298 | 299 | table_scorecard += """


""" 300 | 301 | 302 | for index, row in df.iterrows(): 303 | table_scorecard += """ 304 |
305 |
306 |
"""+row['TABLE_NAME']+"""
307 |
"""+row['TABLE_CATALOG']+"."+row['TABLE_SCHEMA']+"""
308 |
309 |
310 |

311 |
"""+human_format(row['ROW_COUNT'])+"""
312 |

Rows

313 |
314 |
"""+human_bytes(row['BYTES'])+"""
315 |

"""+human_bytes_text(row['BYTES'])+"""

316 |
317 |
"""+"{0:}".format(row['COLUMN_COUNT'])+"""
318 |

Columns 319 |

320 |
321 |
322 |
323 |
Table Type: """+(row['TABLE_TYPE'])+"""
324 |
Owner: """+str(row['TABLE_OWNER'])+"""
325 |
Created On: """+(row['CREATED'].strftime("%Y-%m-%d"))+"""
326 |
327 |
328 |
Time Travel: """+str((row['RETENTION_TIME'])).strip(".0")+"""
329 |
Last Altered: """+(row['LAST_ALTERED'].strftime("%Y-%m-%d"))+"""
330 |
Transient: """+str(row['IS_TRANSIENT'])+"""
331 |
Auto Clustering: """+str(row['AUTO_CLUSTERING_ON'])+"""
332 |
Clustering Key: """+str(row['IS_TRANSIENT'])+"""
333 |
Comment: """+str(row['IS_TRANSIENT'])+"""
334 |
335 |
""" 336 | 337 | st.markdown(table_scorecard, unsafe_allow_html=True) 338 | -------------------------------------------------------------------------------- /snowflake-table-catalog.py: -------------------------------------------------------------------------------- 1 | #from turtle import onclick 2 | import streamlit as st 3 | import snowflake.connector 4 | import pandas as pd 5 | #import streamlit.components.v1 as components 6 | st.set_page_config(layout="wide") 7 | # Initialize connection. 8 | # Uses st.experimental_singleton to only run once. 9 | 10 | 11 | @st.experimental_singleton 12 | def init_connection(): 13 | return snowflake.connector.connect(**st.secrets["snowflake"]) 14 | 15 | 16 | conn = init_connection() 17 | cur = conn.cursor() 18 | 19 | # Perform query. 20 | # Uses st.experimental_memo to only rerun when the query changes or after 10 min. 21 | 22 | @st.experimental_memo(ttl=600) 23 | def run_query(query): 24 | with conn.cursor() as cur: 25 | cur.execute(query) 26 | 27 | dat = cur.fetchall() 28 | df = pd.DataFrame(dat, columns=[col[0] for col in cur.description]) 29 | return df 30 | 31 | 32 | df = run_query("""SELECT 33 | t.TABLE_ID, 34 | t.TABLE_CATALOG, 35 | t.CREATED, 36 | t.TABLE_NAME, 37 | t.TABLE_SCHEMA, 38 | t.TABLE_OWNER, 39 | t.TABLE_TYPE, 40 | t.IS_TRANSIENT, 41 | t.CLUSTERING_KEY, 42 | t.ROW_COUNT, 43 | t.BYTES, 44 | t.RETENTION_TIME, 45 | t.LAST_ALTERED, 46 | t.AUTO_CLUSTERING_ON, 47 | t.COMMENT, 48 | c.column_count 49 | from 50 | SNOWFLAKE.ACCOUNT_USAGE.TABLES t 51 | left join ( 52 | select 53 | table_id, 54 | count(distinct column_id) column_count 55 | from 56 | SNOWFLAKE.ACCOUNT_USAGE.COLUMNS 57 | group by 58 | table_id 59 | ) c on c.table_id = t.table_id 60 | where t.table_schema not like '%ANON_HOL%' and deleted is null;""") 61 | 62 | df2 = df 63 | # if 'df' not in st.session_state: 64 | # st.session_state.df = df 65 | 66 | st.title('Snowflake Table Catalog') 67 | 68 | def human_bytes(B): 69 | """Return the given bytes as a human friendly KB, MB, GB, or TB string.""" 70 | B = float(B) 71 | KB = float(1024) 72 | MB = float(KB ** 2) # 1,048,576 73 | GB = float(KB ** 3) # 1,073,741,824 74 | TB = float(KB ** 4) # 1,099,511,627,776 75 | 76 | if B < KB: 77 | return '{0} {1}'.format(B, '') 78 | elif KB <= B < MB: 79 | return '{0:.2f}'.format(B / KB) 80 | elif MB <= B < GB: 81 | return '{0:.2f}'.format(B / MB) 82 | elif GB <= B < TB: 83 | return '{0:.2f}'.format(B / GB) 84 | elif TB <= B: 85 | return '{0:.2f}'.format(B / TB) 86 | 87 | 88 | def human_bytes_text(B): 89 | """Return the given bytes as a human friendly KB, MB, GB, or TB string.""" 90 | B = float(B) 91 | KB = float(1024) 92 | MB = float(KB ** 2) # 1,048,576 93 | GB = float(KB ** 3) # 1,073,741,824 94 | TB = float(KB ** 4) # 1,099,511,627,776 95 | 96 | if B < KB: 97 | return 'Bytes' 98 | elif KB <= B < MB: 99 | return 'KB' 100 | elif MB <= B < GB: 101 | return 'MB' 102 | elif GB <= B < TB: 103 | return 'GB' 104 | elif TB <= B: 105 | return 'TB' 106 | 107 | 108 | def human_format(num): 109 | magnitude = 0 110 | while abs(num) >= 1000: 111 | magnitude += 1 112 | num /= 1000.0 113 | # add more suffixes if you need them 114 | return ('%.2f%s' % (num, ['', 'K', 'M', 'G', 'T', 'P'][magnitude])).replace('.00', '') 115 | 116 | 117 | def local_css(file_name): 118 | with open(file_name) as f: 119 | st.markdown(f'', unsafe_allow_html=True) 120 | 121 | 122 | def remote_css(url): 123 | st.markdown(f'', 124 | unsafe_allow_html=True) 125 | 126 | 127 | def header_bg(table_type): 128 | if table_type == "BASE TABLE": 129 | return "tablebackground" 130 | elif table_type == "VIEW": 131 | return "viewbackground" 132 | else: 133 | return "mvbackground" 134 | 135 | 136 | remote_css( 137 | "https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css") 138 | 139 | 140 | local_css("style.css") 141 | cb_view_details = st.sidebar.checkbox('View Details') 142 | 143 | if cb_view_details: 144 | view_details="" 145 | else: 146 | view_details="""style="display: none;" """ 147 | 148 | selectbox_orderby = st.sidebar.selectbox("Order By", ('A → Z', 'Z → A', 'Data Size ↓', 'Data Size ↑', 149 | 'Rows ↓', 'Rows ↑', 'Date Created ↓', 'Date Created ↑', 'Date Altered ↓', 'Date Altered ↑')) 150 | #button_clicked = st.button("OK") 151 | 152 | all_option = pd.Series(['All'], index=[9999999]) 153 | 154 | #TABLE_SCHEMA=TABLE_SCHEMA.append({'TABLE_SCHEMA':'All'},ignore_index = True) 155 | 156 | if 'selectbox_database_key' not in st.session_state: 157 | st.session_state.selectbox_database_key = 10 158 | st.session_state.selectbox_schema_key = 20 159 | st.session_state.selectbox_owner_key = 30 160 | st.session_state.selectbox_table_type_key = 40 161 | st.session_state.selectbox_max_rows_key = 50 162 | st.session_state.selectbox_data_size_key = 60 163 | 164 | # Table Catalog/Database 165 | fv_database = df['TABLE_CATALOG'].drop_duplicates() 166 | fv_database = fv_database.append(all_option) 167 | 168 | selectbox_database = st.sidebar.selectbox( 169 | 'Database', fv_database, index=len(fv_database)-1, key=st.session_state.selectbox_database_key) 170 | 171 | if selectbox_database != 'All': 172 | df = df.loc[df['TABLE_CATALOG'] == selectbox_database] 173 | else: 174 | df = df.loc[df['TABLE_CATALOG'].isin(fv_database)] 175 | 176 | # Table Schema 177 | fv_table_schema = df['TABLE_SCHEMA'].drop_duplicates() 178 | fv_table_schema = fv_table_schema.append(all_option) 179 | 180 | selectbox_schema = st.sidebar.selectbox( 181 | "Table Schema", fv_table_schema, len(fv_table_schema)-1, key=st.session_state.selectbox_schema_key) 182 | 183 | if selectbox_schema != 'All': 184 | df = df.loc[df['TABLE_SCHEMA'] == selectbox_schema] 185 | else: 186 | df = df.loc[df['TABLE_SCHEMA'].isin(fv_table_schema)] 187 | 188 | # Table Owner 189 | fv_owner = df['TABLE_OWNER'].drop_duplicates() 190 | fv_owner = fv_owner.append(all_option) 191 | selectbox_owner = st.sidebar.selectbox( 192 | "Table Owner", fv_owner, len(fv_owner)-1, key=st.session_state.selectbox_owner_key) 193 | 194 | if selectbox_owner != 'All': 195 | df = df.loc[df['TABLE_OWNER'] == selectbox_owner] 196 | else: 197 | df = df.loc[df['TABLE_OWNER'].isin(fv_owner)] 198 | 199 | # Table Type 200 | fv_table_type = df['TABLE_TYPE'].drop_duplicates() 201 | selectbox_table_type = st.sidebar.multiselect( 202 | 'Table Type', fv_table_type, fv_table_type, key=st.session_state.selectbox_table_type_key) 203 | 204 | if len(selectbox_table_type) > 0: 205 | df = df.loc[df['TABLE_TYPE'].isin(selectbox_table_type)] 206 | else: 207 | df = df.loc[df['TABLE_TYPE'].isin(fv_table_type)] 208 | 209 | # #!!! This part is disabled since sliders are causing performance issues with large datasets.!!! 210 | # # data size selection 211 | max_data_mb = int(df['BYTES'].max()/1048576) 212 | step_size = 1 213 | 214 | if max_data_mb>1000: 215 | step_size=10 216 | elif max_data_mb>1000000: 217 | step_size=100 218 | elif max_data_mb>1000000000: 219 | step_size=1000 220 | elif max_data_mb>1000000000000: 221 | step_size=10000 222 | 223 | data_size = st.sidebar.slider( 224 | 'Data Size (MB)', 0, max_data_mb+1, (0, max_data_mb+1), key=st.session_state.selectbox_data_size_key, step=step_size) 225 | df = df.loc[(df['BYTES'] >= data_size[0]*1048576) & 226 | (df['BYTES'] <= data_size[1]*1048576)] 227 | 228 | # rows selection 229 | max_rows = int(df['ROW_COUNT'].max()) 230 | step_size = 10 231 | 232 | if max_rows>1000000: 233 | step_size=100 234 | elif max_rows>1000000000: 235 | step_size=1000 236 | elif max_rows>1000000000000: 237 | step_size=10000 238 | 239 | data_rows = st.sidebar.slider('Number of Rows', 0, max_rows+1, 240 | (0, max_rows+1), key=st.session_state.selectbox_max_rows_key, step=step_size) 241 | df = df.loc[(df['ROW_COUNT'] >= data_rows[0]) & 242 | (df['ROW_COUNT'] <= data_rows[1])] 243 | 244 | 245 | def reset_button(): 246 | st.session_state.selectbox_database_key = st.session_state.selectbox_database_key+1 247 | st.session_state.selectbox_schema_key = st.session_state.selectbox_schema_key+1 248 | st.session_state.selectbox_owner_key = st.session_state.selectbox_owner_key+1 249 | st.session_state.selectbox_table_type_key = st.session_state.selectbox_table_type_key+1 250 | st.session_state.selectbox_max_rows_key = st.session_state.selectbox_max_rows_key+1 251 | st.session_state.selectbox_data_size_key = st.session_state.selectbox_data_size_key+1 252 | 253 | 254 | clear_button = st.sidebar.button( 255 | label='Clear Selections', on_click=reset_button) 256 | 257 | if clear_button: 258 | df = df2 259 | 260 | # Card order 261 | orderby_column = '' 262 | orderby_asc = True 263 | 264 | 265 | if selectbox_orderby == 'A → Z': 266 | orderby_column = 'TABLE_NAME' 267 | orderby_asc = True 268 | elif selectbox_orderby == 'Z → A': 269 | orderby_column = 'TABLE_NAME' 270 | orderby_asc = False 271 | elif selectbox_orderby == 'Data Size ↓': 272 | orderby_column = 'BYTES' 273 | orderby_asc = False 274 | elif selectbox_orderby == 'Data Size ↑': 275 | orderby_column = 'BYTES' 276 | orderby_asc = True 277 | elif selectbox_orderby == 'Rows ↓': 278 | orderby_column = 'ROW_COUNT' 279 | orderby_asc = False 280 | elif selectbox_orderby == 'Rows ↑': 281 | orderby_column = 'ROW_COUNT' 282 | orderby_asc = True 283 | elif selectbox_orderby == 'Date Created ↓': 284 | orderby_column = 'CREATED' 285 | orderby_asc = False 286 | elif selectbox_orderby == 'Date Created ↑': 287 | orderby_column = 'CREATED' 288 | orderby_asc = True 289 | elif selectbox_orderby == 'Date Altered ↓': 290 | orderby_column = 'LAST_ALTERED' 291 | orderby_asc = False 292 | elif selectbox_orderby == 'Date Altered ↑': 293 | orderby_column = 'LAST_ALTERED' 294 | orderby_asc = True 295 | 296 | 297 | df.sort_values(by=[orderby_column], inplace=True, ascending=orderby_asc) 298 | 299 | 300 | 301 | table_scorecard = """ 302 |
303 |
304 |
"""+str(df[df['TABLE_TYPE'] == 'BASE TABLE']['TABLE_ID'].count())+""" 305 |
306 |
307 | Tables 308 |
309 |
310 |
311 |
"""+str(df[df['TABLE_TYPE'] == 'VIEW']['TABLE_ID'].count())+""" 312 |
313 |
314 | Views 315 |
316 |
317 |
318 |
"""+str(df[df['TABLE_TYPE'] == 'MATERIALIZED VIEW']['TABLE_ID'].count())+""" 319 |
320 |
321 | Materialized Views 322 |
323 |
324 |
325 |
326 | """+human_format(df['ROW_COUNT'].sum())+""" 327 |
328 |
329 | Rows 330 |
331 |
332 | 333 |
334 |
335 | """+human_bytes(df['BYTES'].sum())+" "+human_bytes_text(df['BYTES'].sum())+""" 336 |
337 |
338 | Data Size 339 |
340 |
341 |
""" 342 | 343 | table_scorecard += """


""" 344 | 345 | 346 | for index, row in df.iterrows(): 347 | table_scorecard += """ 348 |
349 |
350 |
"""+row['TABLE_NAME']+"""
351 |
"""+row['TABLE_CATALOG']+"."+row['TABLE_SCHEMA']+"""
352 |
353 |
354 |

355 |
"""+human_format(row['ROW_COUNT'])+"""
356 |

Rows

357 |
358 |
"""+human_bytes(row['BYTES'])+"""
359 |

"""+human_bytes_text(row['BYTES'])+"""

360 |
361 |
"""+"{0:}".format(row['COLUMN_COUNT'])+"""
362 |

Columns 363 |

364 |
365 |
366 |
367 |
Table Type: """+(row['TABLE_TYPE'])+"""
368 |
Owner: """+str(row['TABLE_OWNER'])+"""
369 |
Created On: """+(row['CREATED'].strftime("%Y-%m-%d"))+"""
370 |
371 |
372 |
Time Travel: """+str((row['RETENTION_TIME'])).strip(".0")+"""
373 |
Last Altered: """+(row['LAST_ALTERED'].strftime("%Y-%m-%d"))+"""
374 |
Transient: """+str(row['IS_TRANSIENT'])+"""
375 |
Auto Clustering: """+str(row['AUTO_CLUSTERING_ON'])+"""
376 |
Clustering Key: """+str(row['IS_TRANSIENT'])+"""
377 |
Comment: """+str(row['IS_TRANSIENT'])+"""
378 |
379 |
""" 380 | 381 | st.markdown(table_scorecard, unsafe_allow_html=True) 382 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | .column { 2 | float: left; 3 | width: 33.33%; 4 | } 5 | 6 | /* Clear floats after the columns */ 7 | .row:after { 8 | content: ""; 9 | display: table; 10 | clear: both; 11 | } 12 | 13 | 14 | .kpi.number { 15 | font-size: 2rem; 16 | text-align: center; 17 | color: rgb(90, 90, 90); 18 | } 19 | 20 | .kpi.text{ 21 | font-size: 1rem; 22 | text-align: center; 23 | color: rgb(90, 90, 90); 24 | padding-top: 10px; 25 | } 26 | 27 | .tablebackground{ 28 | background-color: #BAD2DE !important; 29 | } 30 | 31 | 32 | .viewbackground{ 33 | background-color: #CBE2DA !important; 34 | } 35 | 36 | 37 | .mvbackground{ 38 | background-color: #E5F0EC !important; 39 | } 40 | 41 | 42 | .smallheader{ 43 | font-size: small !important; 44 | } 45 | 46 | #my-container { 47 | height: 100vh; 48 | width: 100%; 49 | } --------------------------------------------------------------------------------