Snowflake Notebooks는 Snowsight의 Workspaces 환경에서 제공되는 Jupyter 호환 .ipynb 기반 개발 환경입니다. Python, SQL, Markdown을 한 파일 안에서 함께 사용할 수 있으며, 데이터 탐색, 머신러닝, 데이터 엔지니어링 워크플로를 Snowflake 데이터 위에서 수행할 수 있습니다.
기존 Snowsight 메뉴의 Notebooks는 Legacy Snowflake Notebooks로 구분되며, 본 실습은 Notebooks in Workspaces 기준으로 진행합니다.

Python 버전과 Runtime 버전은 Notebook Service 생성 시 선택하며, 계정·시점에 따라 선택 가능한 버전이 달라질 수 있습니다.
Warehouse Runtime은 Legacy Notebooks에서만 선택할 수 있습니다.
Compute Pool 크레딧과 Warehouse 크레딧이 각각 발생할 수 있습니다.
Legacy Notebooks처럼 “동일 역할 소유자만 접근”하는 방식과 달리, Workspace는 파일 기반 IDE 환경에 가깝습니다.
실습을 위해 별도의 역할과 데이터베이스를 생성합니다.
Projects > Workspaces 메뉴로 이동합니다
+ Add new를 클릭하고 sql 파일 이름 지정합니다 (예, setup.sql)
다음 SQL 구문을 복사해서 붙여넣고 파란색 Run 버튼을 눌러 역할, 데이터베이스, Compute Pool 권한을 설정합니다.
-- USE ROLE ACCOUNTADMIN ; SET my_user = CURRENT_USER(); -- ============================================ -- 1. DS_ROLE 생성 및 현재 사용자에게 할당 -- ============================================ USE ROLE USERADMIN; CREATE ROLE IF NOT EXISTS DS_ROLE ; GRANT ROLE DS_ROLE TO USER IDENTIFIER($my_user); -- ============================================ -- 2. TESTDB 생성 및 권한 부여 -- ============================================ USE ROLE SYSADMIN; CREATE DATABASE IF NOT EXISTS TESTDB; GRANT USAGE ON DATABASE TESTDB TO ROLE DS_ROLE; GRANT CREATE SCHEMA ON DATABASE TESTDB TO ROLE DS_ROLE; GRANT USAGE ON FUTURE SCHEMAS IN DATABASE TESTDB TO ROLE DS_ROLE; GRANT ALL ON FUTURE TABLES IN DATABASE TESTDB TO ROLE DS_ROLE; GRANT ALL ON FUTURE VIEWS IN DATABASE TESTDB TO ROLE DS_ROLE; GRANT CREATE TABLE ON SCHEMA TESTDB.PUBLIC TO ROLE DS_ROLE; GRANT CREATE STAGE ON SCHEMA TESTDB.PUBLIC TO ROLE DS_ROLE; GRANT USAGE ON ALL SCHEMAS IN DATABASE TESTDB TO ROLE DS_ROLE; GRANT CREATE NOTEBOOK PROJECT ON ALL SCHEMAS IN DATABASE TESTDB TO ROLE DS_ROLE; GRANT USAGE ON SCHEMA TESTDB.PUBLIC TO ROLE DS_ROLE; GRANT CREATE TASK ON SCHEMA TESTDB.PUBLIC TO ROLE DS_ROLE ; GRANT CREATE NOTEBOOK PROJECT ON SCHEMA TESTDB.PUBLIC TO ROLE DS_ROLE; GRANT CREATE NOTEBOOK PROJECT ON FUTURE SCHEMAS IN DATABASE TESTDB TO ROLE DS_ROLE; GRANT MONITOR ON WAREHOUSE COMPUTE_WH TO ROLE DS_ROLE; GRANT USAGE ON WAREHOUSE COMPUTE_WH TO ROLE DS_ROLE; -- ============================================ -- 3. Compute Pool 생성 및 권한 부여 -- ============================================ USE ROLE ACCOUNTADMIN; CREATE COMPUTE POOL IF NOT EXISTS DS_NOTEBOOK_POOL MIN_NODES = 1 MAX_NODES = 3 INSTANCE_FAMILY = CPU_X64_XS AUTO_RESUME = TRUE AUTO_SUSPEND_SECS = 3600; GRANT USAGE ON COMPUTE POOL DS_NOTEBOOK_POOL TO ROLE DS_ROLE; GRANT USAGE ON COMPUTE POOL SYSTEM_COMPUTE_POOL_CPU TO ROLE DS_ROLE; GRANT EXECUTE TASK ON ACCOUNT TO ROLE DS_ROLE;
스크립트 설명
SQL 실행이 끝나면, Workspace 상단의 Role/Warehouse picker에서 역할을 DS_ROLE로 변경합니다. 이후 실습에서 사용할 Query Warehouse도 함께 선택합니다.
새로 로그인할 경우 기본 롤을 설정하기 위해 왼쪽 하단의 로그인한 이름을 클릭하여 Switch Role에서 DS_ROLE로 변경하고 Set as default를 설정 합니다

Snowflake Notebook을 생성합니다.
Workspace에서 노트북 파일을 생성합니다. Legacy Notebooks 메뉴의 + Notebook과 달리, Workspace에서는 .ipynb 파일을 직접 만듭니다.
+ Add new 클릭 > Notebook 선택합니다Workspace Notebooks는 Container Runtime 기반이므로, 코드를 실행할 Notebook Service가 필요합니다. Notebook Service는 Compute Pool 위에서 동작하는 Snowflake-managed 서비스이며, 사용자별로 생성됩니다. 한 서비스에 여러 .ipynb 노트북을 연결해 공유할 수 있습니다.
서비스가 RUNNING 상태인 동안 Compute Pool 크레딧이 발생하며, idle timeout 이후 자동으로 suspend됩니다. SQL 실행에 사용되는 Query Warehouse 크레딧은 별도로 발생합니다.
Connect버튼 옆에 아래 화살표 ⌄ 를 클릭 > Create new service 클릭DS_NOTEBOOK_SERVICE )SNOWFLAKE.SNOWPARK.PYPI_SHARED_REPOSITORY3.12 2.5)**DS_NOTEBOOK_POOL** 선택1 hour)Create and connect 클릭하면 해당 노트북이 Notebook Service에 연결됩니다
마크다운 셀을 활용하면 단순한 코드 실행을 넘어 완전한 분석 문서를 만들 수 있어, 데이터 과학자와 엔지니어들이 더 효과적으로 작업을 문서화하고 공유할 수 있습니다.
셀은 기존 셀 하단 또는 노트북 하단의 + SQL, + Python, + Markdown 버튼으로 추가할 수 있습니다. Markdown 셀은 편집 후 Done Editing(☑️) 을 눌러 렌더링된 결과를 확인합니다.
코드 셀은 Run cell(▶️) 로 실행합니다.
상단의 Python을 Markdown으로 변경하여 새 마크다운 셀을 사용합니다.
## 1. 테스트 테이블 생성 (SQL) 테스트용 테이블을 생성합니다. 이미 존재하는 경우 스킵합니다.

Python 코드만으로 SQL구문을 작성하려면 SQL문자열이 길어질 수록 가독성이 떨어집니다. SQL 문법에 최적화된 하이라이팅을 지원하여 쿼리 구조를 한눈에 파악하기 쉽습니다. Snowflake Notebooks의 가장 큰 강점 중 하나는 SQL 셀의 결과를 Python에서 즉시 사용하거나, 반대의 경우도 가능하다는 점입니다.
Workspace 노트북은 database/schema 컨텍스트가 자동으로 설정되지 않으므로, SQL 셀에서 반드시 USE DATABASE, USE SCHEMA를 실행합니다. 상단 picker에서 선택한 Role과 Warehouse도 함께 확인하세요.
하단의 + SQL을 클릭하여 새 SQL 셀을 생성합니다.
USE DATABASE TESTDB ; USE SCHEMA PUBLIC ; CREATE TABLE IF NOT EXISTS test_table ( id INTEGER AUTOINCREMENT, timestamp TIMESTAMP, value FLOAT, category VARCHAR(50) );

위와 같은 방법으로 Markdown 셀과 Python 셀을 생성합니다.
S1 하단의 + Markdown을 클릭하여 새 마크다운 셀을 생성합니다.
## 2. 랜덤 데이터 생성 (Python) 현재 시간과 랜덤한 값, 카테고리를 생성합니다.
하단의 + Python을 클릭하여 새 Python 셀을 생성합니다.
import random from datetime import datetime v_current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') random_value = random.uniform(0, 100) categories = ['A', 'B', 'C', 'D'] random_category = random.choice(categories) print(f"Time: {v_current_time}") print(f"Value: {random_value}") print(f"Category: {random_category}")

Jinja 형식 {{변수명}}을 사용하면 SQL 셀에서 Python 변수를 참조할 수 있습니다.
Python 셀 P1에서 생성한 v_current_time, random_value, random_category를 SQL 셀 S2에서 사용해 데이터를 삽입합니다.
P1셀 하단의 + Markdown을 클릭하여 새 마크다운 셀을 생성합니다.
## 3. 데이터 삽입 (SQL) 이전 셀에서 생성한 Python 변수를 Jinja 템플릿으로 참조하여 테이블에 데이터를 삽입합니다.
+ SQL을 클릭하여 새 SQL 셀을 생성합니다.
INSERT INTO test_table (timestamp, value, category) VALUES ('{{v_current_time}}', {{random_value}}, '{{random_category}}')

S2셀 하단의 + Markdown을 클릭하여 새 마크다운 셀을 생성합니다.
## 4. 데이터 조회 (SQL) 입력된 데이터를 SQL 쿼리로 조회합니다.
+ SQL을 클릭하여 새 SQL 셀을 생성합니다.
SELECT * FROM test_table ORDER BY timestamp DESC

Workspace 노트북에서는 SQL 셀 결과가 dataframe_x 형태의 pandas DataFrame pointer 로 노출됩니다. 결과 영역의 tooltip을 확인하면 Python에서 참조할 정확한 이름을 볼 수 있습니다.
S3 하단의 + Markdown을 클릭하여 새 마크다운 셀을 생성합니다.
## 5. 데이터 시각화 (Python) 조회한 데이터에 대해 차트로 시각화합니다.
+ Python을 클릭하여 새 Python 셀을 생성합니다.
import matplotlib.pyplot as plt import pandas as pd import seaborn as sns df = dataframe_3 fig, axes = plt.subplots(2, 2, figsize=(16, 10)) fig.subplots_adjust(hspace=0.4, wspace=0.3) # Color palette colors = sns.color_palette('Set2', len(df['CATEGORY'].unique())) # 1. Count by Category category_counts = df.groupby('CATEGORY').size().sort_index() bars1 = axes[0, 0].bar(category_counts.index, category_counts.values, color=colors, edgecolor='black', linewidth=2, width=0.6) for bar in bars1: height = bar.get_height() axes[0, 0].text(bar.get_x() + bar.get_width()/2., height, f'{int(height)}', ha='center', va='bottom', fontweight='bold', fontsize=11) axes[0, 0].set_xlabel('Category', fontsize=13, fontweight='bold') axes[0, 0].set_ylabel('Count', fontsize=13, fontweight='bold') axes[0, 0].set_title('Count by Category', fontsize=15, fontweight='bold', pad=20) axes[0, 0].grid(True, alpha=0.3, axis='y', linestyle='--') # 2. Average by Category category_avg = df.groupby('CATEGORY')['VALUE'].mean().sort_index() bars2 = axes[0, 1].bar(category_avg.index, category_avg.values, color=colors, edgecolor='black', linewidth=2, width=0.6) for bar in bars2: height = bar.get_height() axes[0, 1].text(bar.get_x() + bar.get_width()/2., height, f'{height:.1f}', ha='center', va='bottom', fontweight='bold', fontsize=11) axes[0, 1].set_xlabel('Category', fontsize=13, fontweight='bold') axes[0, 1].set_ylabel('Average Value', fontsize=13, fontweight='bold') axes[0, 1].set_title('Average by Category', fontsize=15, fontweight='bold', pad=20) axes[0, 1].grid(True, alpha=0.3, axis='y', linestyle='--') # 3. Minimum by Category category_min = df.groupby('CATEGORY')['VALUE'].min().sort_index() bars3 = axes[1, 0].bar(category_min.index, category_min.values, color=colors, edgecolor='black', linewidth=2, width=0.6) for bar in bars3: height = bar.get_height() axes[1, 0].text(bar.get_x() + bar.get_width()/2., height, f'{height:.1f}', ha='center', va='bottom', fontweight='bold', fontsize=11) axes[1, 0].set_xlabel('Category', fontsize=13, fontweight='bold') axes[1, 0].set_ylabel('Min Value', fontsize=13, fontweight='bold') axes[1, 0].set_title('Minimum by Category', fontsize=15, fontweight='bold', pad=20) axes[1, 0].grid(True, alpha=0.3, axis='y', linestyle='--') # 4. Maximum by Category category_max = df.groupby('CATEGORY')['VALUE'].max().sort_index() bars4 = axes[1, 1].bar(category_max.index, category_max.values, color=colors, edgecolor='black', linewidth=2, width=0.6) for bar in bars4: height = bar.get_height() axes[1, 1].text(bar.get_x() + bar.get_width()/2., height, f'{height:.1f}', ha='center', va='bottom', fontweight='bold', fontsize=11) axes[1, 1].set_xlabel('Category', fontsize=13, fontweight='bold') axes[1, 1].set_ylabel('Max Value', fontsize=13, fontweight='bold') axes[1, 1].set_title('Maximum by Category', fontsize=15, fontweight='bold', pad=20) axes[1, 1].grid(True, alpha=0.3, axis='y', linestyle='--') plt.show() # Summary statistics print("=" * 60) print("Data Summary".center(60)) print("=" * 60) print(f"Total Records: {len(df):,}") print(f"Value Range: {df['VALUE'].min():.2f} ~ {df['VALUE'].max():.2f}") print(f"Mean Value: {df['VALUE'].mean():.2f}") print(f"Standard Deviation: {df['VALUE'].std():.2f}") print("\n" + "=" * 60) print("Category Statistics".center(60)) print("=" * 60) category_stats = df.groupby('CATEGORY')['VALUE'].agg(['count', 'mean', 'min', 'max']).round(2) category_stats.columns = ['Count', 'Average', 'Minimum', 'Maximum'] print(category_stats.to_string())

상단의 Run를 클릭하여 모든 셀을 순서대로 실행할 수 있습니다.
CREATE TABLE IF NOT EXISTS를 사용했기 때문에 Run all을 여러 번 실행하면 데이터가 누적됩니다.

결과를 HTML로 저장하기
Workspace 노트북에서는 셀 실행 결과가 .ipynb 파일에 저장되지 않습니다. 분석 결과와 출력을 그대로 공유하려면 HTML로 내보내는 것이 편합니다. Export as HTML로 저장하면 셀 코드, 실행 결과, 표·차트 출력을 하나의 파일로 묶을 수 있습니다. Workspace에서 바로 확인한 결과를 그대로 전달할 때 유용합니다
...(더보기) 메뉴를 클릭합니다.
본 글은 Snowflake의 차세대 노트북 환경인 Notebooks in Workspaces 기준으로 작성되었습니다. Legacy Notebooks와의 주요 차이는 다음과 같습니다.