diff --git a/.github/workflows/notebook-jupyter.yml b/.github/workflows/notebook-jupyter.yml
new file mode 100644
index 00000000..1ee13747
--- /dev/null
+++ b/.github/workflows/notebook-jupyter.yml
@@ -0,0 +1,94 @@
+name: pandas
+
+on:
+ pull_request:
+ branches: ~
+ paths:
+ - '.github/workflows/notebook-jupyter.yml'
+ - 'notebook/jupyter/**'
+ - '/requirements.txt'
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/notebook-jupyter.yml'
+ - 'notebook/jupyter/**'
+ - '/requirements.txt'
+
+ # Allow job to be triggered manually.
+ workflow_dispatch:
+
+ # Run job each night after CrateDB nightly has been published.
+ schedule:
+ - cron: '0 3 * * *'
+
+# Cancel in-progress jobs when pushing to the same branch.
+concurrency:
+ cancel-in-progress: true
+ group: ${{ github.workflow }}-${{ github.ref }}
+
+jobs:
+ test:
+ name: "
+ Python: ${{ matrix.python-version }}
+ CrateDB: ${{ matrix.cratedb-version }}
+ on ${{ matrix.os }}"
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ 'ubuntu-latest' ]
+ python-version: [ '3.8', '3.13' ]
+ cratedb-version: [ 'nightly' ]
+
+ env:
+ OS_TYPE: ${{ matrix.os }}
+ PYTHON_VERSION: ${{ matrix.python-version }}
+ UV_SYSTEM_PYTHON: true
+
+ services:
+ cratedb:
+ image: crate/crate:${{ matrix.cratedb-version }}
+ ports:
+ - 4200:4200
+ - 5432:5432
+ env:
+ CRATE_HEAP_SIZE: 4g
+
+ steps:
+
+ - name: Acquire sources
+ uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: ${{ matrix.python-version }}
+ architecture: x64
+ cache: 'pip'
+ cache-dependency-path: |
+ notebook/jupyter/pyproject.toml
+
+ - name: Set up uv
+ uses: astral-sh/setup-uv@v5
+ with:
+ cache-dependency-glob: |
+ notebook/jupyter/pyproject.toml
+ cache-suffix: ${{ matrix.python-version }}
+ enable-cache: true
+ version: "latest"
+
+ - name: Install project
+ run: |
+ uv pip install notebook/jupyter[all]
+
+ - name: Validate notebook/jupyter
+ run: |
+ cd notebook/jupyter
+ pytest
+
+ - name: Build docs for notebook/jupyter
+ run: |
+ cd notebook/jupyter
+ poe check
+ poe docs-html
+ poe docs-linkcheck
diff --git a/.gitignore b/.gitignore
index 7d27b66d..ed0a062a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
.idea
.venv*
__pycache__
+_build
.coverage*
.env
.DS_Store
@@ -14,3 +15,4 @@ logs.log
*.tmp
*.swp
*.bak
+*.egg-info
diff --git a/notebook/jupyter/README.md b/notebook/jupyter/README.md
new file mode 100644
index 00000000..1f73e408
--- /dev/null
+++ b/notebook/jupyter/README.md
@@ -0,0 +1,8 @@
+# CrateDB Jupyter Examples
+
+A few examples using CrateDB from Jupyter Notebooks.
+If you are here already, we recommend to check out the [JupySQL] examples
+at least.
+
+
+[JupySQL]: https://jupysql.ploomber.io/
diff --git a/notebook/jupyter/conftest.py b/notebook/jupyter/conftest.py
new file mode 100644
index 00000000..08a84357
--- /dev/null
+++ b/notebook/jupyter/conftest.py
@@ -0,0 +1,31 @@
+import os
+from pathlib import Path
+
+import pytest
+import sqlalchemy as sa
+from pueblo.testing.notebook import generate_tests
+
+
+def pytest_generate_tests(metafunc):
+ """
+ Generate pytest test case per Jupyter Notebook.
+ """
+ here = Path(__file__).parent
+ generate_tests(metafunc, path=here)
+
+
+@pytest.fixture(autouse=True)
+def reset_database_tables():
+ """
+ Before running a test case, reset relevant tables in database.
+ """
+
+ connection_string = os.environ.get("CRATEDB_CONNECTION_STRING")
+
+ engine = sa.create_engine(connection_string, echo=os.environ.get("DEBUG"))
+ connection = engine.connect()
+
+ reset_tables = []
+
+ for table in reset_tables:
+ connection.execute(sa.text(f"DROP TABLE IF EXISTS {table};"))
diff --git a/notebook/jupyter/docs/conf.py b/notebook/jupyter/docs/conf.py
new file mode 100644
index 00000000..182bc7d6
--- /dev/null
+++ b/notebook/jupyter/docs/conf.py
@@ -0,0 +1,6 @@
+project = "CrateDB Jupyter Examples"
+copyright = "2023-2025, The CrateDB Developers"
+author = "The CrateDB Developers"
+
+html_theme = "furo"
+extensions = ["myst_nb"]
diff --git a/notebook/jupyter/docs/index.md b/notebook/jupyter/docs/index.md
new file mode 100644
index 00000000..03ca6b4c
--- /dev/null
+++ b/notebook/jupyter/docs/index.md
@@ -0,0 +1,14 @@
+# CrateDB Jupyter Examples
+
+This is a little Sphinx documentation stub that includes Jupyter Notebooks,
+by using [MyST-NB].
+
+```{toctree}
+:maxdepth: 2
+:glob:
+
+*
+```
+
+
+[MyST-NB]: https://myst-nb.readthedocs.io/
diff --git a/notebook/jupyter/docs/jupysql.ipynb b/notebook/jupyter/docs/jupysql.ipynb
new file mode 120000
index 00000000..57a77f15
--- /dev/null
+++ b/notebook/jupyter/docs/jupysql.ipynb
@@ -0,0 +1 @@
+../jupysql.ipynb
\ No newline at end of file
diff --git a/notebook/jupyter/jupysql.ipynb b/notebook/jupyter/jupysql.ipynb
new file mode 100644
index 00000000..0271543d
--- /dev/null
+++ b/notebook/jupyter/jupysql.ipynb
@@ -0,0 +1,2524 @@
+{
+ "cells": [
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "# CrateDB JupySQL Example\n",
+ "\n",
+ "[JupySQL], the successor of [ipython-sql], enables running SQL in\n",
+ "Jupyter/IPython per `%sql` and `%%sql` magics. It aims to become\n",
+ "a full-fledged SQL client, compatible with all major databases.\n",
+ "\n",
+ "## Setup\n",
+ "\n",
+ "To start a single-node instance of CrateDB for evaluation purposes, you can use\n",
+ "Docker or Podman.\n",
+ "```bash\n",
+ "docker run --rm -it --name=cratedb \\\n",
+ " --publish=4200:4200 --publish=5432:5432 \\\n",
+ " --env=CRATE_HEAP_SIZE=2g crate/crate:nightly \\\n",
+ " -Cdiscovery.type=single-node\n",
+ "```\n",
+ "\n",
+ "[ipython-sql]: https://github.com/catherinedevlin/ipython-sql\n",
+ "[JupySQL]: https://jupysql.ploomber.io/"
+ ],
+ "id": "fb6563fb550c0c45"
+ },
+ {
+ "metadata": {
+ "tags": [
+ "hide-output"
+ ],
+ "ExecuteTime": {
+ "end_time": "2025-01-12T22:07:23.911648Z",
+ "start_time": "2025-01-12T22:07:11.441321Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "# Load extension.\n",
+ "%reload_ext sql\n",
+ "\n",
+ "# Connect to CrateDB.\n",
+ "%sql crate://"
+ ],
+ "id": "a3bee8cfa5cb0f71",
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Connecting to 'crate://'"
+ ],
+ "text/html": [
+ "Connecting to 'crate://'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "execution_count": 1
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": "## Basic querying",
+ "id": "e335963343a76925"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-12T21:51:48.898994Z",
+ "start_time": "2025-01-12T21:51:48.846645Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "# Invoke SQL query and display results.\n",
+ "%sql SELECT * FROM sys.summits ORDER BY height DESC;"
+ ],
+ "id": "d04e59f04fce1b2f",
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Running query in 'crate://'"
+ ],
+ "text/html": [
+ "Running query in 'crate://'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "1605 rows affected."
+ ],
+ "text/html": [
+ "1605 rows affected."
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "+----------------+---------------------+---------+--------------+--------+----------------+------------+---------------+----------------------+\n",
+ "| classification | coordinates | country | first_ascent | height | mountain | prominence | range | region |\n",
+ "+----------------+---------------------+---------+--------------+--------+----------------+------------+---------------+----------------------+\n",
+ "| I/B-07.V-B | [6.86444, 45.8325] | FR/IT | 1786 | 4808 | Mont Blanc | 4695 | U-Savoy/Aosta | Mont Blanc massif |\n",
+ "| I/B-09.III-A | [7.86694, 45.93694] | CH | 1855 | 4634 | Monte Rosa | 2165 | Valais | Monte Rosa Alps |\n",
+ "| I/B-09.V-A | [7.85889, 46.09389] | CH | 1858 | 4545 | Dom | 1046 | Valais | Mischabel |\n",
+ "| I/B-09.III-A | [7.83556, 45.92222] | CH/IT | 1861 | 4527 | Liskamm | 376 | Valais/Aosta | Monte Rosa Alps |\n",
+ "| I/B-09.II-D | [7.71583, 46.10139] | CH | 1861 | 4506 | Weisshorn | 1235 | Valais | Weisshorn-Matterhorn |\n",
+ "| I/B-09.II-A | [7.65861, 45.97639] | CH/IT | 1865 | 4478 | Matterhorn | 1042 | Valais/Aosta | Weisshorn-Matterhorn |\n",
+ "| I/B-09.II-C | [7.61194, 46.03417] | CH | 1862 | 4357 | Dent Blanche | 915 | Valais | Weisshorn-Matterhorn |\n",
+ "| I/B-09.I-B | [7.29917, 45.9375] | CH | 1859 | 4314 | Grand Combin | 1517 | Valais | Grand Combin Alps |\n",
+ "| I/B-12.II-A | [8.12611, 46.53722] | CH | 1829 | 4274 | Finsteraarhorn | 2280 | Bern/Valais | Bernese Alps |\n",
+ "| I/B-09.II-D | [7.69028, 46.065] | CH | 1864 | 4221 | Zinalrothorn | 490 | Valais | Weisshorn-Matterhorn |\n",
+ "+----------------+---------------------+---------+--------------+--------+----------------+------------+---------------+----------------------+\n",
+ "Truncated to displaylimit of 10."
+ ],
+ "text/html": [
+ "
\n",
+ " \n",
+ " \n",
+ " classification | \n",
+ " coordinates | \n",
+ " country | \n",
+ " first_ascent | \n",
+ " height | \n",
+ " mountain | \n",
+ " prominence | \n",
+ " range | \n",
+ " region | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " I/B-07.V-B | \n",
+ " [6.86444, 45.8325] | \n",
+ " FR/IT | \n",
+ " 1786 | \n",
+ " 4808 | \n",
+ " Mont Blanc | \n",
+ " 4695 | \n",
+ " U-Savoy/Aosta | \n",
+ " Mont Blanc massif | \n",
+ "
\n",
+ " \n",
+ " I/B-09.III-A | \n",
+ " [7.86694, 45.93694] | \n",
+ " CH | \n",
+ " 1855 | \n",
+ " 4634 | \n",
+ " Monte Rosa | \n",
+ " 2165 | \n",
+ " Valais | \n",
+ " Monte Rosa Alps | \n",
+ "
\n",
+ " \n",
+ " I/B-09.V-A | \n",
+ " [7.85889, 46.09389] | \n",
+ " CH | \n",
+ " 1858 | \n",
+ " 4545 | \n",
+ " Dom | \n",
+ " 1046 | \n",
+ " Valais | \n",
+ " Mischabel | \n",
+ "
\n",
+ " \n",
+ " I/B-09.III-A | \n",
+ " [7.83556, 45.92222] | \n",
+ " CH/IT | \n",
+ " 1861 | \n",
+ " 4527 | \n",
+ " Liskamm | \n",
+ " 376 | \n",
+ " Valais/Aosta | \n",
+ " Monte Rosa Alps | \n",
+ "
\n",
+ " \n",
+ " I/B-09.II-D | \n",
+ " [7.71583, 46.10139] | \n",
+ " CH | \n",
+ " 1861 | \n",
+ " 4506 | \n",
+ " Weisshorn | \n",
+ " 1235 | \n",
+ " Valais | \n",
+ " Weisshorn-Matterhorn | \n",
+ "
\n",
+ " \n",
+ " I/B-09.II-A | \n",
+ " [7.65861, 45.97639] | \n",
+ " CH/IT | \n",
+ " 1865 | \n",
+ " 4478 | \n",
+ " Matterhorn | \n",
+ " 1042 | \n",
+ " Valais/Aosta | \n",
+ " Weisshorn-Matterhorn | \n",
+ "
\n",
+ " \n",
+ " I/B-09.II-C | \n",
+ " [7.61194, 46.03417] | \n",
+ " CH | \n",
+ " 1862 | \n",
+ " 4357 | \n",
+ " Dent Blanche | \n",
+ " 915 | \n",
+ " Valais | \n",
+ " Weisshorn-Matterhorn | \n",
+ "
\n",
+ " \n",
+ " I/B-09.I-B | \n",
+ " [7.29917, 45.9375] | \n",
+ " CH | \n",
+ " 1859 | \n",
+ " 4314 | \n",
+ " Grand Combin | \n",
+ " 1517 | \n",
+ " Valais | \n",
+ " Grand Combin Alps | \n",
+ "
\n",
+ " \n",
+ " I/B-12.II-A | \n",
+ " [8.12611, 46.53722] | \n",
+ " CH | \n",
+ " 1829 | \n",
+ " 4274 | \n",
+ " Finsteraarhorn | \n",
+ " 2280 | \n",
+ " Bern/Valais | \n",
+ " Bernese Alps | \n",
+ "
\n",
+ " \n",
+ " I/B-09.II-D | \n",
+ " [7.69028, 46.065] | \n",
+ " CH | \n",
+ " 1864 | \n",
+ " 4221 | \n",
+ " Zinalrothorn | \n",
+ " 490 | \n",
+ " Valais | \n",
+ " Weisshorn-Matterhorn | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "Truncated to displaylimit of 10."
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 4
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "## DDL reflection\n",
+ "\n",
+ "Inspect tables and table definitions."
+ ],
+ "id": "a9908656a3d953c6"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-12T21:55:26.204987Z",
+ "start_time": "2025-01-12T21:55:26.190942Z"
+ }
+ },
+ "cell_type": "code",
+ "source": "%sqlcmd tables --schema sys",
+ "id": "4fb7412a52fe9c73",
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "+------------------+\n",
+ "| Name |\n",
+ "+------------------+\n",
+ "| allocations |\n",
+ "| checks |\n",
+ "| cluster |\n",
+ "| health |\n",
+ "| jobs |\n",
+ "| jobs_log |\n",
+ "| jobs_metrics |\n",
+ "| node_checks |\n",
+ "| nodes |\n",
+ "| operations |\n",
+ "| operations_log |\n",
+ "| privileges |\n",
+ "| repositories |\n",
+ "| roles |\n",
+ "| segments |\n",
+ "| sessions |\n",
+ "| shards |\n",
+ "| snapshot_restore |\n",
+ "| snapshots |\n",
+ "| summits |\n",
+ "| users |\n",
+ "+------------------+"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " Name | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " allocations | \n",
+ "
\n",
+ " \n",
+ " checks | \n",
+ "
\n",
+ " \n",
+ " cluster | \n",
+ "
\n",
+ " \n",
+ " health | \n",
+ "
\n",
+ " \n",
+ " jobs | \n",
+ "
\n",
+ " \n",
+ " jobs_log | \n",
+ "
\n",
+ " \n",
+ " jobs_metrics | \n",
+ "
\n",
+ " \n",
+ " node_checks | \n",
+ "
\n",
+ " \n",
+ " nodes | \n",
+ "
\n",
+ " \n",
+ " operations | \n",
+ "
\n",
+ " \n",
+ " operations_log | \n",
+ "
\n",
+ " \n",
+ " privileges | \n",
+ "
\n",
+ " \n",
+ " repositories | \n",
+ "
\n",
+ " \n",
+ " roles | \n",
+ "
\n",
+ " \n",
+ " segments | \n",
+ "
\n",
+ " \n",
+ " sessions | \n",
+ "
\n",
+ " \n",
+ " shards | \n",
+ "
\n",
+ " \n",
+ " snapshot_restore | \n",
+ "
\n",
+ " \n",
+ " snapshots | \n",
+ "
\n",
+ " \n",
+ " summits | \n",
+ "
\n",
+ " \n",
+ " users | \n",
+ "
\n",
+ " \n",
+ "
"
+ ]
+ },
+ "execution_count": 22,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 22
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-12T21:55:12.887615Z",
+ "start_time": "2025-01-12T21:55:12.790165Z"
+ }
+ },
+ "cell_type": "code",
+ "source": "%sqlcmd columns --table sys.summits",
+ "id": "4e180a5b9132c1ca",
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "+----------------+-------------------+----------+\n",
+ "| name | type | nullable |\n",
+ "+----------------+-------------------+----------+\n",
+ "| classification | VARCHAR | True |\n",
+ "| coordinates | UserDefinedType() | True |\n",
+ "| country | VARCHAR | True |\n",
+ "| first_ascent | INTEGER | True |\n",
+ "| height | INTEGER | True |\n",
+ "| mountain | VARCHAR | True |\n",
+ "| prominence | INTEGER | True |\n",
+ "| range | VARCHAR | True |\n",
+ "| region | VARCHAR | True |\n",
+ "+----------------+-------------------+----------+"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " name | \n",
+ " type | \n",
+ " nullable | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " classification | \n",
+ " VARCHAR | \n",
+ " True | \n",
+ "
\n",
+ " \n",
+ " coordinates | \n",
+ " UserDefinedType() | \n",
+ " True | \n",
+ "
\n",
+ " \n",
+ " country | \n",
+ " VARCHAR | \n",
+ " True | \n",
+ "
\n",
+ " \n",
+ " first_ascent | \n",
+ " INTEGER | \n",
+ " True | \n",
+ "
\n",
+ " \n",
+ " height | \n",
+ " INTEGER | \n",
+ " True | \n",
+ "
\n",
+ " \n",
+ " mountain | \n",
+ " VARCHAR | \n",
+ " True | \n",
+ "
\n",
+ " \n",
+ " prominence | \n",
+ " INTEGER | \n",
+ " True | \n",
+ "
\n",
+ " \n",
+ " range | \n",
+ " VARCHAR | \n",
+ " True | \n",
+ "
\n",
+ " \n",
+ " region | \n",
+ " VARCHAR | \n",
+ " True | \n",
+ "
\n",
+ " \n",
+ "
"
+ ]
+ },
+ "execution_count": 21,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 21
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "## Variable expansion\n",
+ "\n",
+ "JupySQL supports variable expansion of arguments in the form of `{{variable}}`.\n",
+ "Let’s see an example of parametrizing `table` and `schema`:"
+ ],
+ "id": "33014aebc88d5f45"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-12T22:00:13.550895Z",
+ "start_time": "2025-01-12T22:00:13.454615Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "schema = \"sys\"\n",
+ "table = \"jobs\"\n",
+ "\n",
+ "%sqlcmd columns --table {{table}} --schema {{schema}}"
+ ],
+ "id": "10b46da81f93fc41",
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "+----------+------------------+----------+\n",
+ "| name | type | nullable |\n",
+ "+----------+------------------+----------+\n",
+ "| id | VARCHAR | True |\n",
+ "| node | ObjectTypeImpl() | True |\n",
+ "| started | TIMESTAMP | True |\n",
+ "| stmt | VARCHAR | True |\n",
+ "| username | VARCHAR | True |\n",
+ "+----------+------------------+----------+"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " name | \n",
+ " type | \n",
+ " nullable | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " id | \n",
+ " VARCHAR | \n",
+ " True | \n",
+ "
\n",
+ " \n",
+ " node | \n",
+ " ObjectTypeImpl() | \n",
+ " True | \n",
+ "
\n",
+ " \n",
+ " started | \n",
+ " TIMESTAMP | \n",
+ " True | \n",
+ "
\n",
+ " \n",
+ " stmt | \n",
+ " VARCHAR | \n",
+ " True | \n",
+ "
\n",
+ " \n",
+ " username | \n",
+ " VARCHAR | \n",
+ " True | \n",
+ "
\n",
+ " \n",
+ "
"
+ ]
+ },
+ "execution_count": 24,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 24
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "## Plotting\n",
+ "\n",
+ "The [ggplot API] is structured around the principles of the grammar of graphics,\n",
+ "and allows you to build any graph using the same components: a data set, a\n",
+ "coordinate system, and geoms (geometric objects). It is suitable for plotting\n",
+ "larger-than-memory datasets on any laptop.\n",
+ "\n",
+ "[ggplot API]: https://jupysql.ploomber.io/en/latest/user-guide/ggplot.html"
+ ],
+ "id": "47164a73a987463e"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-13T00:11:54.228327Z",
+ "start_time": "2025-01-13T00:11:49.775313Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "# Acquire and import data.\n",
+ "from csvkit.utilities.csvsql import CSVSQL\n",
+ "from urllib.request import urlretrieve\n",
+ "from pathlib import Path\n",
+ "\n",
+ "if not Path(\"diamonds.csv\").is_file():\n",
+ " urlretrieve(\n",
+ " \"https://github.com/tidyverse/ggplot2/raw/refs/heads/main/data-raw/diamonds.csv\",\n",
+ " \"diamonds.csv\",\n",
+ " )\n",
+ "\n",
+ "CSVSQL(args=[\"--db\", \"crate://\", \"--insert\", \"diamonds.csv\", \"--overwrite\"]).run()"
+ ],
+ "id": "9cdd96bf40b37d8c",
+ "outputs": [],
+ "execution_count": 106
+ },
+ {
+ "metadata": {
+ "tags": [
+ "hide-output"
+ ],
+ "ExecuteTime": {
+ "end_time": "2025-01-12T23:54:50.255142Z",
+ "start_time": "2025-01-12T23:54:50.106092Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "from sql.ggplot import ggplot, aes, geom_histogram\n",
+ "\n",
+ "(ggplot(\"diamonds\", aes(x=\"cut\")) + geom_histogram())"
+ ],
+ "id": "352765e762d44252",
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 89,
+ "metadata": {},
+ "output_type": "execute_result"
+ },
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ],
+ "image/png": "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fPksR44c7pJameAYyWkWni4AACB7yNCWJo1BV2CaMWOGLVq0yCpUqBC1v1atWpYrVy5buHBheJuWJNASA/Xq1XPX9fPrr7+OmuWmmXgKRFWrVg2XiTxGUCY4hroAdVuRZdRdqOtBGQAAkL3lzOguOc2Me++999xaTcH4IY0RUguQfnbu3NktBaDB4QpCms2mIKOZc6IlChSO7rrrLhsyZIg7xtNPP+2OHbQEde3a1c2K69Onj913330uoE2bNs3NqAvoNjp16mS1a9e2q666ykaMGOGWPrj33nsz6OwAAIDMJEND05gxY9zPa665Jmr7+PHj7Z577nG/Dx8+3M1k06KWmrGmWW+vvPJKuKy61dS199BDD7kwVaBAARd+Bg0aFC6jFiwFJK35NHLkSNcF+Prrr7tjBdq3b++WKND6TgpeNWvWdMsRJB8cDgAAsqdMtU5TLGOdJsAf6zSlD9ZpArLwOk0AAACZFaEJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAADAA6EJAAAgs4emjz/+2G688UYrW7asxcXF2cyZM6P233PPPW575OW6666LKvPbb79Zx44drWDBgla4cGHr3Lmz7du3L6rMV199ZY0aNbK8efNauXLlbMiQISnqMn36dKtcubIrc9lll9mcOXPO0r0GAACxKEND0/79+61GjRo2evTo45ZR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XDRo0MDGjBljf/zB91ml9WM4WLwWp/ac1wLAeowOGjTIjh49mq710MrV27dvt2rVqlksu/HGG+26665Ldd8nn3zizvNXX32V7l9WP3ToULviiiusQIECbpHHGjVq2NNPP23btm1L17rEApYcwCnRE378+PFR20qUKHHCv8mXL5+7nOhJqxfk7OjHH390AUktcX/5y1/ssssuc1OAv/76axs3bpydd955dtNNN2V0NZGNBc95fUH5nDlzrFu3bpYrVy73VVLp9TzWV1yl9nVWsaZz587Wrl07+/nnn+3888+P2qdzrIWVq1evfsrHPd1zr/9pixYtXFDTd6nqtUiv55s2bbLJkyfbyy+/7L5nFf9FSxNOid7Q9eIVeRk5cqR7s9enFH0i/POf/2z79u07bvfcgAED3MKhr7/+ulWoUMG1rmRXOlc5c+a0lStX2p/+9CerUqWKXXjhhXbzzTfb7Nmz3SdT2bJli9t2zjnnuK88UNnk35OolqmLLrrIvXhWqlTJ3n777aj93333nTVu3Nid76pVq9qCBQssO9Nq/3fffbc7p2XKlLFhw4al+qbSu3dvF171+K5Tp44tWbIkvP/XX3+1Dh06uP358+d3zwO92WTF53z58uXtoYcesubNm9v7778fbrX7n//5H/edXXrMib7LTI9PPeeLFi3qHrfqXgsEf6cPCWpVVbmg9erxxx93f6NAEfnhLHn3XGpd/upmjvzez+B15s0337QLLrjA/Z/1fDt27JgNGTLE3aeSJUu6+qeXG264wYUS1T+SXi+nT5/uQpV8+umn1qhRI/dhU6+pjzzyiHu8BhISEuy5555zj1+9HjzwwAPWtGnTFGsMarFmvR7o68BSM3z4cHdbixYtcrehrxLTubr66qtt7Nix7n8U+Vx45JFH3DnTa0jDhg3tiy++iDreRx995L5NQ48ZPaf69u0b1Srp85zL7AhNOGPx8fH2t7/9zdatW2dvvfWWewL26dPnhH+jLr133nnH3n333Ww7TkFvuPPnz3ef3PWGnBq9Ceg7EPXGoy+E1ouSwo5aqNTtGZgxY4br4uvVq5d988039uCDD7qv/1m8eLHbr2O0bdvWvYB+/vnn7gXxiSeesOxMb9A6n++99577PygMrV69OqqM3oSWLVtmU6ZMcZ/Gb7vtNtfyogAqBw8edG80Crg673rzuuuuu2zFihWWVemNXC0bojfjjRs3usfkrFmz7MiRI9ayZUs799xzXXfTZ5995t4gdc6CvxG9Rqjr5+OPP7aXXnrJLbKoQFGkSBH3+Ozatat7DKtF5kz88MMP9sEHH7hvdlCYfeONN6x169buuPrfv/jii64bSreZHvQBSaFBoSlyiUQFJoU5BXDVWedLLVJ6zE2dOtUFm+SB6K9//avrRluzZo0988wzdv/999ukSZNcuAn8/e9/d4FegSo1OifXXnutXX755anujwyhek1/55133Gu8nifqqtX/Wq9LsnXrVmvVqpVdeeWV9uWXX7oPcTrfzz///Ck95zI9LW4J+OjUqVMoR44coQIFCoQvt956a4py06dPDxUrVix8ffz48aFChQqFrz/77LOhXLlyhRITE0PZ2fLly/WqGXr33XejtuvcBee3T58+ofnz57vzvmXLlnCZdevWub9dsWKFu16/fv1Qly5doo5z2223hVq1auV+nzdvXihnzpyhrVu3hvd/8MEH7hgzZswIZafH8M033xzau3dvKHfu3KFp06aF9/3666+hfPnyhR599FF3/d///rc775HnTJo1axbq16/fcW+jdevWoV69eoWvX3311eFjxur5kqSkpNCCBQtCefLkCfXu3dvtK1WqVOjQoUPh8m+//XaoUqVKrmxA+3Ve9RgMjlm+fPnQsWPHwmX0N40aNQpfP3r0qHv8T5482V3ftGmTe6yuWbMm1dcU0eM48i1NrzP58+cP7dmzJ7ytZcuWoYSEhBS3PXjw4FB6Wb9+vavn4sWLw9t03++88073e+fOnUMPPPBA1N988sknofj4+NCBAwfcdZ2/Nm3aRJXRviJFioSmTp0a3la9evXQgAEDjluXvHnzhh555JGobTpu8PpTr149t23fvn3uNXvixInhcocPHw6VLVs2NGTIEHf9ySefTPG/Hz16dOicc85x59vnORcLGNOEU9KkSRP3CSKgFpIPP/zQ9Xtv2LDB9uzZ45pj9Qlcg5jVZZEaNfWfbCxUdqVWCrUMdezY0X1qXL9+vWui1yWg7jV1T2ifPtnpp1o5Iml8grpOJTiGulEC9erVs+xKn+bV8qHutoC6hYIuJtG4Mn36v+SSS6L+Vv+TYsWKud+1X10Y06ZNc5+0dUztP97jPhapBUmtRWpF0uPyjjvucF1faiFVd2TkWBq1MKgVWS1NkfR6oHMeuPTSS10LdUDddJGDvDWGSec4MTHxjOqubqzIuuh2dOzkt32mt3MqKleubPXr13fdhpo5pvOlVjl1UQbnUC1MEydODP+NWqV07jXWSF34ovFPkdRlplZOHVfdo2rBUeunulJPxSuvvOK60dR7oJZA0f9O//8GDRqEy2lcm7ri9Noi+qnXlMjWKZVX16Na9n7//feTPudiAaEJp0QhSc2ykWMN1KyusQ4aG6AngZqS1TevJ8jx3jyO1x2Vneg86gVG3RuRNKZJTjR4HmefXuz1Brtq1Sr3M5JChGjWkYLpiBEjwuP6NH06sisqq3xQUjhS6FYX0/Gexzpn6q6MfMMPRH5I0htuJD0PUtumoJAahZ7k3wCmN/XkzvR2zha9Pj788MM2evRoN3ZLYxE1jig4h+qa1Pih5DTe6ESvoeqi0zguhRQdV91y+oB6PBdffHGK1x+NNRK9liMlxjThjOgNRS84GtBXt25d96mcaap+9Ela4wlGjRoVNcgzOX2y1OBaXQL/+te/bNeuXa7FKSij8SORdD1yv/5e07YDy5cvt+xKb1J684wcy6JPwpFLY2ich1qS1AqhgBt5CWZy6RxrvNmdd97pxpco8Ga15TWCD0p6w44MTKnRtHWN99Jg4eTnTFPZ04oC2N69e6OeN7E0NlItQQp+GoP0v//7v3bfffeFW2h0DvX8Tn7+dDnZDDkFd7VAvfbaa+7YOu6JaAyVxqNpXNSJBBNMPot4jVFI1UDwyNcYjf+LDLMqr5Y+Dez3ec7FAkITzoieyHryaGqqBidrxpYGGcO/KVzdmXqh04BPNXHrk58GcKq7Uy0cmq2kF0N116nJXd13GkyqT6ZBE70GWGpwqVoE9KalwbUaZK+ZX6JjKNB26tTJNf+rO+Cpp56y7EotRfq0r/OmQcnqxtCsrshuG50vnXOda51LdY3o3KsrWgO/g0/qetNZunSp+9+phSD5rMbsROerePHiLkjqMaZzpsG+ajU500HdkdTFo1bsJ5980nUdKSAkn5GW2R9/msihZRv0QUaPvYAmaOjxpIHfCoJ6PmvgdPKB4Mej1qYXXnjBhZdbbrnlhGV79OjhutSaNWvmWkz1+qL/2bx589wA+qCFVcFZvQmPP/64G1SvUNelSxc3BCOY8aeZifpgphY0vXapzhrg37NnT/e88nnOxYLYqi0yHX261hu0ZqFoTIKa5VnXw58+felTnkKNXkB1PhWEFEIVeDStWJ9A9QKkmUVaMkBl1aKhkBXQFG696GlGjcaLvPrqq655XmMmRC9MmmF34MABNw5BL6zpOdU6M1LXmqZ1a1kHnVNNoVbXUiSdQ4UmzUrU2AudZ326DrpJNPNKLQOaRaRzrRao7Lx4poKMxsHo/Gi2plof9EapMU2aGp9W1HWkDxZaNypY5kHjrGKJzotaWvTYiRxrqHWaNMNMLTB6fKrFs3///lFlTtZ6pBZB/TzZci7arxmQCmp6rOs5oP+Zupg1Hiny2wIUxNq1a+fGTekxr7FYCld6XRLN0tP/Qx8s9DqmGZC6j3qOnMpzLrOL02jwjK4EAAA4cxpnqg9jCvcKN0hbhCYAAGKchklo7Te1UKuLLfkYR6QNuucAAIhxCkma+aYWJsaVnj20NAEAAHigpQkAAMADoQkAAMADoQkAAMADoQkAAMADoQkAAMADoQkAzoC+vqNw4cIZXQ0A6YDQBAAA4IHQBCDbS0pKsiFDhrgvoM6TJ4/77jR9N5++bFbf/bdr165wWX2Jqrbp6yq0/95777Xdu3e7bbrE2negAfCX8xTKAkCWpC9Lfu2112z48OHuS0T1zfP6pvaTqV+/vo0YMcJ9oerGjRvdNn2bO4CsidAEIFvbu3evjRw50kaNGmWdOnVy2/SFpwpPakk6kdy5c1uhQoVcC1Pp0qXTqcYAMgrdcwCytfXr19uhQ4esWbNmGV0VAJkcoQlAtpYvX77j7ouP//8vkZFf0alvkweQPRGaAGRrF198sQtOCxcuTLGvRIkS7qfGOEUOBE/eRXfs2LF0qCmAjMaYJgDZWt68ee2JJ56wPn36uADUoEED++WXX2zdunV29913W7ly5dyMOM2m+/bbb23YsGFRf5+QkGD79u1zoatGjRqWP39+dwGQ9dDSBCDbe+aZZ6xXr15uFlyVKlWsffv2lpiYaLly5bLJkye7mXTVq1e3F1980Z5//vkUM+i6du3q/kYtU1q6AEDWFBeK7KwHAABAqmhpAgAA8EBoAgAA8EBoAgAA8EBoAgAA8EBoAgAA8EBoAgAA8EBoAgAA8EBoAgAA8EBoAgAA8EBoAgAA8EBoAgAA8EBoAgAAsJP7fx2j/X2mdqvNAAAAAElFTkSuQmCC"
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "execution_count": 89
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-12T22:29:16.746106Z",
+ "start_time": "2025-01-12T22:29:16.435407Z"
+ }
+ },
+ "cell_type": "code",
+ "source": "(ggplot(\"diamonds\", aes(x=[\"color\", \"carat\"])) + geom_histogram(bins=30))",
+ "id": "6e22c8377a28ccc2",
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 11,
+ "metadata": {},
+ "output_type": "execute_result"
+ },
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ],
+ "image/png": "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"
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "execution_count": 11
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "## Parameterizing SQL queries\n",
+ "\n",
+ "JupySQL provides powerful SQL parametrization capabilities, either using traditional\n",
+ "`:variable`-style interpolation for SQLAlchemy-compatible databases, or using\n",
+ "Jinja templates for enabling SQL query parametrization with `{{variable}}`.\n",
+ "\n",
+ "The latter provides a versatile little programming system providing data filtering,\n",
+ "SQL generation, macros, snippets, and optionally combining all with Python code.\n",
+ "\n",
+ "See [JupySQL templating].\n",
+ "\n",
+ "[JupySQL templating]: https://jupysql.ploomber.io/en/latest/user-guide/template.html"
+ ],
+ "id": "cac01128c3aae33f"
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": "### Traditional",
+ "id": "b5a0c621f6571c5d"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-12T22:52:25.657179Z",
+ "start_time": "2025-01-12T22:52:25.648986Z"
+ }
+ },
+ "cell_type": "code",
+ "outputs": [],
+ "execution_count": 29,
+ "source": [
+ "%config SqlMagic.displaylimit = 20\n",
+ "%config SqlMagic.named_parameters = \"enabled\"\n",
+ "\n",
+ "country = \"CH\"\n",
+ "region = \"%matterhorn%\""
+ ],
+ "id": "665bde489df6c6b3"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-12T22:52:33.155918Z",
+ "start_time": "2025-01-12T22:52:33.133616Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "%%sql\n",
+ "SELECT *\n",
+ "FROM sys.summits\n",
+ "WHERE\n",
+ " country = :country\n",
+ " AND\n",
+ " region ILIKE :region\n",
+ "ORDER BY height DESC;"
+ ],
+ "id": "6c35015aa02ae97",
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Running query in 'crate://'"
+ ],
+ "text/html": [
+ "Running query in 'crate://'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "12 rows affected."
+ ],
+ "text/html": [
+ "12 rows affected."
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "+----------------+---------------------+---------+--------------+--------+-----------------+------------+--------+----------------------+\n",
+ "| classification | coordinates | country | first_ascent | height | mountain | prominence | range | region |\n",
+ "+----------------+---------------------+---------+--------------+--------+-----------------+------------+--------+----------------------+\n",
+ "| I/B-09.II-D | [7.71583, 46.10139] | CH | 1861 | 4506 | Weisshorn | 1235 | Valais | Weisshorn-Matterhorn |\n",
+ "| I/B-09.II-C | [7.61194, 46.03417] | CH | 1862 | 4357 | Dent Blanche | 915 | Valais | Weisshorn-Matterhorn |\n",
+ "| I/B-09.II-D | [7.69028, 46.065] | CH | 1864 | 4221 | Zinalrothorn | 490 | Valais | Weisshorn-Matterhorn |\n",
+ "| I/B-09.II-D | [7.66806, 46.03861] | CH | 1865 | 4063 | Ober Gabelhorn | 536 | Valais | Weisshorn-Matterhorn |\n",
+ "| I/B-09.II-C | [7.61139, 46.05194] | CH | 1865 | 3962 | Grand Cornier | 431 | Valais | Weisshorn-Matterhorn |\n",
+ "| I/B-09.II-D | [7.74556, 46.12583] | CH | 1865 | 3833 | Brunegghorn | 293 | Valais | Weisshorn-Matterhorn |\n",
+ "| I/B-09.II-C | [7.63056, 46.02694] | CH | 1870 | 3789 | Pointe de Zinal | 301 | Valais | Weisshorn-Matterhorn |\n",
+ "| I/B-09.II-A | [7.52306, 46.03944] | CH | 1871 | 3676 | Dent de Perroc | 408 | Valais | Weisshorn-Matterhorn |\n",
+ "| I/B-09.II-D | [7.67111, 46.1425] | CH | 1863 | 3609 | Les Diablons | 379 | Valais | Weisshorn-Matterhorn |\n",
+ "| I/B-09.II-C | [7.52528, 46.13861] | CH | 1835 | 3254 | Sasseneire | 386 | Valais | Weisshorn-Matterhorn |\n",
+ "| I/B-09.II-D | [7.75667, 46.21667] | CH | None | 3201 | Schwarzhorn | 308 | Valais | Weisshorn-Matterhorn |\n",
+ "| I/B-09.II-C | [7.51806, 46.16778] | CH | None | 3149 | Becs de Bosson | 362 | Valais | Weisshorn-Matterhorn |\n",
+ "+----------------+---------------------+---------+--------------+--------+-----------------+------------+--------+----------------------+"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " classification | \n",
+ " coordinates | \n",
+ " country | \n",
+ " first_ascent | \n",
+ " height | \n",
+ " mountain | \n",
+ " prominence | \n",
+ " range | \n",
+ " region | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " I/B-09.II-D | \n",
+ " [7.71583, 46.10139] | \n",
+ " CH | \n",
+ " 1861 | \n",
+ " 4506 | \n",
+ " Weisshorn | \n",
+ " 1235 | \n",
+ " Valais | \n",
+ " Weisshorn-Matterhorn | \n",
+ "
\n",
+ " \n",
+ " I/B-09.II-C | \n",
+ " [7.61194, 46.03417] | \n",
+ " CH | \n",
+ " 1862 | \n",
+ " 4357 | \n",
+ " Dent Blanche | \n",
+ " 915 | \n",
+ " Valais | \n",
+ " Weisshorn-Matterhorn | \n",
+ "
\n",
+ " \n",
+ " I/B-09.II-D | \n",
+ " [7.69028, 46.065] | \n",
+ " CH | \n",
+ " 1864 | \n",
+ " 4221 | \n",
+ " Zinalrothorn | \n",
+ " 490 | \n",
+ " Valais | \n",
+ " Weisshorn-Matterhorn | \n",
+ "
\n",
+ " \n",
+ " I/B-09.II-D | \n",
+ " [7.66806, 46.03861] | \n",
+ " CH | \n",
+ " 1865 | \n",
+ " 4063 | \n",
+ " Ober Gabelhorn | \n",
+ " 536 | \n",
+ " Valais | \n",
+ " Weisshorn-Matterhorn | \n",
+ "
\n",
+ " \n",
+ " I/B-09.II-C | \n",
+ " [7.61139, 46.05194] | \n",
+ " CH | \n",
+ " 1865 | \n",
+ " 3962 | \n",
+ " Grand Cornier | \n",
+ " 431 | \n",
+ " Valais | \n",
+ " Weisshorn-Matterhorn | \n",
+ "
\n",
+ " \n",
+ " I/B-09.II-D | \n",
+ " [7.74556, 46.12583] | \n",
+ " CH | \n",
+ " 1865 | \n",
+ " 3833 | \n",
+ " Brunegghorn | \n",
+ " 293 | \n",
+ " Valais | \n",
+ " Weisshorn-Matterhorn | \n",
+ "
\n",
+ " \n",
+ " I/B-09.II-C | \n",
+ " [7.63056, 46.02694] | \n",
+ " CH | \n",
+ " 1870 | \n",
+ " 3789 | \n",
+ " Pointe de Zinal | \n",
+ " 301 | \n",
+ " Valais | \n",
+ " Weisshorn-Matterhorn | \n",
+ "
\n",
+ " \n",
+ " I/B-09.II-A | \n",
+ " [7.52306, 46.03944] | \n",
+ " CH | \n",
+ " 1871 | \n",
+ " 3676 | \n",
+ " Dent de Perroc | \n",
+ " 408 | \n",
+ " Valais | \n",
+ " Weisshorn-Matterhorn | \n",
+ "
\n",
+ " \n",
+ " I/B-09.II-D | \n",
+ " [7.67111, 46.1425] | \n",
+ " CH | \n",
+ " 1863 | \n",
+ " 3609 | \n",
+ " Les Diablons | \n",
+ " 379 | \n",
+ " Valais | \n",
+ " Weisshorn-Matterhorn | \n",
+ "
\n",
+ " \n",
+ " I/B-09.II-C | \n",
+ " [7.52528, 46.13861] | \n",
+ " CH | \n",
+ " 1835 | \n",
+ " 3254 | \n",
+ " Sasseneire | \n",
+ " 386 | \n",
+ " Valais | \n",
+ " Weisshorn-Matterhorn | \n",
+ "
\n",
+ " \n",
+ " I/B-09.II-D | \n",
+ " [7.75667, 46.21667] | \n",
+ " CH | \n",
+ " None | \n",
+ " 3201 | \n",
+ " Schwarzhorn | \n",
+ " 308 | \n",
+ " Valais | \n",
+ " Weisshorn-Matterhorn | \n",
+ "
\n",
+ " \n",
+ " I/B-09.II-C | \n",
+ " [7.51806, 46.16778] | \n",
+ " CH | \n",
+ " None | \n",
+ " 3149 | \n",
+ " Becs de Bosson | \n",
+ " 362 | \n",
+ " Valais | \n",
+ " Weisshorn-Matterhorn | \n",
+ "
\n",
+ " \n",
+ "
"
+ ]
+ },
+ "execution_count": 31,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 31
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": "### Jinja",
+ "id": "18df23f4f78b5428"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-12T22:55:18.601726Z",
+ "start_time": "2025-01-12T22:55:18.594163Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "%config SqlMagic.displaylimit = 5\n",
+ "%config SqlMagic.named_parameters = \"disabled\"\n",
+ "\n",
+ "country = \"CH\"\n",
+ "region = \"%matterhorn%\""
+ ],
+ "id": "e119190312afae40",
+ "outputs": [],
+ "execution_count": 36
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-12T22:55:19.274595Z",
+ "start_time": "2025-01-12T22:55:19.257308Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "%%sql\n",
+ "SELECT *\n",
+ "FROM sys.summits\n",
+ "WHERE\n",
+ " country = '{{ country }}'\n",
+ " AND\n",
+ " region ILIKE '{{ region }}'\n",
+ "ORDER BY height DESC;"
+ ],
+ "id": "a5b0a0495c9fc6c1",
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Running query in 'crate://'"
+ ],
+ "text/html": [
+ "Running query in 'crate://'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "12 rows affected."
+ ],
+ "text/html": [
+ "12 rows affected."
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "+----------------+---------------------+---------+--------------+--------+----------------+------------+--------+----------------------+\n",
+ "| classification | coordinates | country | first_ascent | height | mountain | prominence | range | region |\n",
+ "+----------------+---------------------+---------+--------------+--------+----------------+------------+--------+----------------------+\n",
+ "| I/B-09.II-D | [7.71583, 46.10139] | CH | 1861 | 4506 | Weisshorn | 1235 | Valais | Weisshorn-Matterhorn |\n",
+ "| I/B-09.II-C | [7.61194, 46.03417] | CH | 1862 | 4357 | Dent Blanche | 915 | Valais | Weisshorn-Matterhorn |\n",
+ "| I/B-09.II-D | [7.69028, 46.065] | CH | 1864 | 4221 | Zinalrothorn | 490 | Valais | Weisshorn-Matterhorn |\n",
+ "| I/B-09.II-D | [7.66806, 46.03861] | CH | 1865 | 4063 | Ober Gabelhorn | 536 | Valais | Weisshorn-Matterhorn |\n",
+ "| I/B-09.II-C | [7.61139, 46.05194] | CH | 1865 | 3962 | Grand Cornier | 431 | Valais | Weisshorn-Matterhorn |\n",
+ "+----------------+---------------------+---------+--------------+--------+----------------+------------+--------+----------------------+\n",
+ "Truncated to displaylimit of 5."
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " classification | \n",
+ " coordinates | \n",
+ " country | \n",
+ " first_ascent | \n",
+ " height | \n",
+ " mountain | \n",
+ " prominence | \n",
+ " range | \n",
+ " region | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " I/B-09.II-D | \n",
+ " [7.71583, 46.10139] | \n",
+ " CH | \n",
+ " 1861 | \n",
+ " 4506 | \n",
+ " Weisshorn | \n",
+ " 1235 | \n",
+ " Valais | \n",
+ " Weisshorn-Matterhorn | \n",
+ "
\n",
+ " \n",
+ " I/B-09.II-C | \n",
+ " [7.61194, 46.03417] | \n",
+ " CH | \n",
+ " 1862 | \n",
+ " 4357 | \n",
+ " Dent Blanche | \n",
+ " 915 | \n",
+ " Valais | \n",
+ " Weisshorn-Matterhorn | \n",
+ "
\n",
+ " \n",
+ " I/B-09.II-D | \n",
+ " [7.69028, 46.065] | \n",
+ " CH | \n",
+ " 1864 | \n",
+ " 4221 | \n",
+ " Zinalrothorn | \n",
+ " 490 | \n",
+ " Valais | \n",
+ " Weisshorn-Matterhorn | \n",
+ "
\n",
+ " \n",
+ " I/B-09.II-D | \n",
+ " [7.66806, 46.03861] | \n",
+ " CH | \n",
+ " 1865 | \n",
+ " 4063 | \n",
+ " Ober Gabelhorn | \n",
+ " 536 | \n",
+ " Valais | \n",
+ " Weisshorn-Matterhorn | \n",
+ "
\n",
+ " \n",
+ " I/B-09.II-C | \n",
+ " [7.61139, 46.05194] | \n",
+ " CH | \n",
+ " 1865 | \n",
+ " 3962 | \n",
+ " Grand Cornier | \n",
+ " 431 | \n",
+ " Valais | \n",
+ " Weisshorn-Matterhorn | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "Truncated to displaylimit of 5."
+ ]
+ },
+ "execution_count": 37,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 37
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": "## Parameterizing arguments",
+ "id": "d70451beba191b58"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-12T23:04:19.208071Z",
+ "start_time": "2025-01-12T23:04:19.202485Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "%config SqlMagic.displaylimit = 3\n",
+ "\n",
+ "table = \"summits\""
+ ],
+ "id": "43a1cd177b996602",
+ "outputs": [],
+ "execution_count": 64
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-12T22:59:03.323504Z",
+ "start_time": "2025-01-12T22:59:03.290720Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "%%sql --save {{table}}\n",
+ "SELECT *\n",
+ "FROM sys.summits"
+ ],
+ "id": "24f32a4b4a61e720",
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Running query in 'crate://'"
+ ],
+ "text/html": [
+ "Running query in 'crate://'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "1605 rows affected."
+ ],
+ "text/html": [
+ "1605 rows affected."
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "+----------------+---------------------+---------+--------------+--------+------------+------------+---------------+-------------------+\n",
+ "| classification | coordinates | country | first_ascent | height | mountain | prominence | range | region |\n",
+ "+----------------+---------------------+---------+--------------+--------+------------+------------+---------------+-------------------+\n",
+ "| I/B-07.V-B | [6.86444, 45.8325] | FR/IT | 1786 | 4808 | Mont Blanc | 4695 | U-Savoy/Aosta | Mont Blanc massif |\n",
+ "| I/B-09.III-A | [7.86694, 45.93694] | CH | 1855 | 4634 | Monte Rosa | 2165 | Valais | Monte Rosa Alps |\n",
+ "| I/B-09.V-A | [7.85889, 46.09389] | CH | 1858 | 4545 | Dom | 1046 | Valais | Mischabel |\n",
+ "+----------------+---------------------+---------+--------------+--------+------------+------------+---------------+-------------------+\n",
+ "Truncated to displaylimit of 3."
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " classification | \n",
+ " coordinates | \n",
+ " country | \n",
+ " first_ascent | \n",
+ " height | \n",
+ " mountain | \n",
+ " prominence | \n",
+ " range | \n",
+ " region | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " I/B-07.V-B | \n",
+ " [6.86444, 45.8325] | \n",
+ " FR/IT | \n",
+ " 1786 | \n",
+ " 4808 | \n",
+ " Mont Blanc | \n",
+ " 4695 | \n",
+ " U-Savoy/Aosta | \n",
+ " Mont Blanc massif | \n",
+ "
\n",
+ " \n",
+ " I/B-09.III-A | \n",
+ " [7.86694, 45.93694] | \n",
+ " CH | \n",
+ " 1855 | \n",
+ " 4634 | \n",
+ " Monte Rosa | \n",
+ " 2165 | \n",
+ " Valais | \n",
+ " Monte Rosa Alps | \n",
+ "
\n",
+ " \n",
+ " I/B-09.V-A | \n",
+ " [7.85889, 46.09389] | \n",
+ " CH | \n",
+ " 1858 | \n",
+ " 4545 | \n",
+ " Dom | \n",
+ " 1046 | \n",
+ " Valais | \n",
+ " Mischabel | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "Truncated to displaylimit of 3."
+ ]
+ },
+ "execution_count": 41,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 41
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": "## Profile and explore",
+ "id": "65e9ec4e4e65a30c"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-12T23:07:17.175366Z",
+ "start_time": "2025-01-12T23:07:16.893241Z"
+ }
+ },
+ "cell_type": "code",
+ "source": "%sqlcmd profile --table sys.summits",
+ "id": "97ae14e6a3b18ba",
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "+--------+----------------+----------------------+---------+--------------+-----------+-------------+------------+-------------+-------------------+\n",
+ "| | classification | coordinates | country | first_ascent | height | mountain | prominence | range | region |\n",
+ "+--------+----------------+----------------------+---------+--------------+-----------+-------------+------------+-------------+-------------------+\n",
+ "| count | 1605 | | 1605 | 600 | 1605 | 1605 | 1605 | 1605 | 1605 |\n",
+ "| unique | 261 | | 17 | 111 | 1059 | 1583 | 614 | 170 | 136 |\n",
+ "| top | II/A-15.VI-A | [10.61417, 46.30861] | IT | nan | nan | Schwarzhorn | nan | North Tyrol | Massif des Écrins |\n",
+ "| freq | 21 | 2 | 436 | nan | nan | 4 | nan | 150 | 48 |\n",
+ "| mean | nan | nan | nan | 1859.1533 | 2811.9184 | nan | 556.1564 | nan | nan |\n",
+ "| min | nan | nan | nan | 1358 | 1996 | nan | 170 | nan | nan |\n",
+ "| max | nan | nan | nan | 1905 | 4808 | nan | 4695 | nan | nan |\n",
+ "+--------+----------------+----------------------+---------+--------------+-----------+-------------+------------+-------------+-------------------+"
+ ],
+ "text/html": [
+ " Following statistics are not available in\n",
+ " crate-python: STD, 25%, 50%, 75%
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " classification | \n",
+ " coordinates | \n",
+ " country | \n",
+ " first_ascent | \n",
+ " height | \n",
+ " mountain | \n",
+ " prominence | \n",
+ " range | \n",
+ " region | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " count | \n",
+ " 1605 | \n",
+ " | \n",
+ " 1605 | \n",
+ " 600 | \n",
+ " 1605 | \n",
+ " 1605 | \n",
+ " 1605 | \n",
+ " 1605 | \n",
+ " 1605 | \n",
+ "
\n",
+ " \n",
+ " unique | \n",
+ " 261 | \n",
+ " | \n",
+ " 17 | \n",
+ " 111 | \n",
+ " 1059 | \n",
+ " 1583 | \n",
+ " 614 | \n",
+ " 170 | \n",
+ " 136 | \n",
+ "
\n",
+ " \n",
+ " top | \n",
+ " II/A-15.VI-A | \n",
+ " [10.61417, 46.30861] | \n",
+ " IT | \n",
+ " nan | \n",
+ " nan | \n",
+ " Schwarzhorn | \n",
+ " nan | \n",
+ " North Tyrol | \n",
+ " Massif des Écrins | \n",
+ "
\n",
+ " \n",
+ " freq | \n",
+ " 21 | \n",
+ " 2 | \n",
+ " 436 | \n",
+ " nan | \n",
+ " nan | \n",
+ " 4 | \n",
+ " nan | \n",
+ " 150 | \n",
+ " 48 | \n",
+ "
\n",
+ " \n",
+ " mean | \n",
+ " nan | \n",
+ " nan | \n",
+ " nan | \n",
+ " 1859.1533 | \n",
+ " 2811.9184 | \n",
+ " nan | \n",
+ " 556.1564 | \n",
+ " nan | \n",
+ " nan | \n",
+ "
\n",
+ " \n",
+ " min | \n",
+ " nan | \n",
+ " nan | \n",
+ " nan | \n",
+ " 1358 | \n",
+ " 1996 | \n",
+ " nan | \n",
+ " 170 | \n",
+ " nan | \n",
+ " nan | \n",
+ "
\n",
+ " \n",
+ " max | \n",
+ " nan | \n",
+ " nan | \n",
+ " nan | \n",
+ " 1905 | \n",
+ " 4808 | \n",
+ " nan | \n",
+ " 4695 | \n",
+ " nan | \n",
+ " nan | \n",
+ "
\n",
+ " \n",
+ "
"
+ ]
+ },
+ "execution_count": 75,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 75
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-12T23:07:22.106803Z",
+ "start_time": "2025-01-12T23:07:22.078122Z"
+ }
+ },
+ "cell_type": "code",
+ "source": "%sqlcmd explore --table sys.summits",
+ "id": "bf014db76f9759d9",
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " "
+ ]
+ },
+ "execution_count": 76,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 76
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "## pandas\n",
+ "\n",
+ "If you have installed `pandas`, you can use a result set’s `.DataFrame()` method,\n",
+ "see [JupySQL pandas integration].\n",
+ "\n",
+ "[JupySQL pandas integration]: https://jupysql.ploomber.io/en/latest/integrations/pandas.html"
+ ],
+ "id": "a37b12af1fab1feb"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-12T23:24:37.917507Z",
+ "start_time": "2025-01-12T23:24:37.878963Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "result = %sql SELECT * FROM sys.summits\n",
+ "df = result.DataFrame()\n",
+ "df"
+ ],
+ "id": "aa43766196f2b313",
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Generating CTE with stored snippets: 'summits'"
+ ],
+ "text/html": [
+ "Generating CTE with stored snippets: 'summits'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Running query in 'crate://'"
+ ],
+ "text/html": [
+ "Running query in 'crate://'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "1605 rows affected."
+ ],
+ "text/html": [
+ "1605 rows affected."
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ " classification coordinates country first_ascent height \\\n",
+ "0 I/B-07.V-B [6.86444, 45.8325] FR/IT 1786.0 4808 \n",
+ "1 I/B-09.III-A [7.86694, 45.93694] CH 1855.0 4634 \n",
+ "2 I/B-09.V-A [7.85889, 46.09389] CH 1858.0 4545 \n",
+ "3 I/B-09.III-A [7.83556, 45.92222] CH/IT 1861.0 4527 \n",
+ "4 I/B-09.II-D [7.71583, 46.10139] CH 1861.0 4506 \n",
+ "... ... ... ... ... ... \n",
+ "1600 II/C-29.I-B [9.61583, 45.92972] IT NaN 1999 \n",
+ "1601 I/B-08.III-C [6.57556, 46.12861] FR NaN 1999 \n",
+ "1602 II/C-33.I-B [13.36389, 46.53222] IT NaN 1999 \n",
+ "1603 I/A-02.I-E [6.59083, 44.02139] FR NaN 1996 \n",
+ "1604 II/B-23.II-C [12.43, 47.47611] AT NaN 1996 \n",
+ "\n",
+ " mountain prominence range \\\n",
+ "0 Mont Blanc 4695 U-Savoy/Aosta \n",
+ "1 Monte Rosa 2165 Valais \n",
+ "2 Dom 1046 Valais \n",
+ "3 Liskamm 376 Valais/Aosta \n",
+ "4 Weisshorn 1235 Valais \n",
+ "... ... ... ... \n",
+ "1600 Monte Venturosa 471 Bergamo \n",
+ "1601 Pointe de Marcelly 440 Upper Savoy \n",
+ "1602 Monte Scinauz 369 Udine \n",
+ "1603 Puy de Rent 556 Alpes-de-Haute-Provence \n",
+ "1604 Kitzbüheler Horn 516 North Tyrol \n",
+ "\n",
+ " region \n",
+ "0 Mont Blanc massif \n",
+ "1 Monte Rosa Alps \n",
+ "2 Mischabel \n",
+ "3 Monte Rosa Alps \n",
+ "4 Weisshorn-Matterhorn \n",
+ "... ... \n",
+ "1600 Bergamo Alps \n",
+ "1601 Chablais Alps \n",
+ "1602 Carnic Alps \n",
+ "1603 Maritime Alps \n",
+ "1604 Kitzbühel Alps \n",
+ "\n",
+ "[1605 rows x 9 columns]"
+ ],
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " classification | \n",
+ " coordinates | \n",
+ " country | \n",
+ " first_ascent | \n",
+ " height | \n",
+ " mountain | \n",
+ " prominence | \n",
+ " range | \n",
+ " region | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 0 | \n",
+ " I/B-07.V-B | \n",
+ " [6.86444, 45.8325] | \n",
+ " FR/IT | \n",
+ " 1786.0 | \n",
+ " 4808 | \n",
+ " Mont Blanc | \n",
+ " 4695 | \n",
+ " U-Savoy/Aosta | \n",
+ " Mont Blanc massif | \n",
+ "
\n",
+ " \n",
+ " 1 | \n",
+ " I/B-09.III-A | \n",
+ " [7.86694, 45.93694] | \n",
+ " CH | \n",
+ " 1855.0 | \n",
+ " 4634 | \n",
+ " Monte Rosa | \n",
+ " 2165 | \n",
+ " Valais | \n",
+ " Monte Rosa Alps | \n",
+ "
\n",
+ " \n",
+ " 2 | \n",
+ " I/B-09.V-A | \n",
+ " [7.85889, 46.09389] | \n",
+ " CH | \n",
+ " 1858.0 | \n",
+ " 4545 | \n",
+ " Dom | \n",
+ " 1046 | \n",
+ " Valais | \n",
+ " Mischabel | \n",
+ "
\n",
+ " \n",
+ " 3 | \n",
+ " I/B-09.III-A | \n",
+ " [7.83556, 45.92222] | \n",
+ " CH/IT | \n",
+ " 1861.0 | \n",
+ " 4527 | \n",
+ " Liskamm | \n",
+ " 376 | \n",
+ " Valais/Aosta | \n",
+ " Monte Rosa Alps | \n",
+ "
\n",
+ " \n",
+ " 4 | \n",
+ " I/B-09.II-D | \n",
+ " [7.71583, 46.10139] | \n",
+ " CH | \n",
+ " 1861.0 | \n",
+ " 4506 | \n",
+ " Weisshorn | \n",
+ " 1235 | \n",
+ " Valais | \n",
+ " Weisshorn-Matterhorn | \n",
+ "
\n",
+ " \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ "
\n",
+ " \n",
+ " 1600 | \n",
+ " II/C-29.I-B | \n",
+ " [9.61583, 45.92972] | \n",
+ " IT | \n",
+ " NaN | \n",
+ " 1999 | \n",
+ " Monte Venturosa | \n",
+ " 471 | \n",
+ " Bergamo | \n",
+ " Bergamo Alps | \n",
+ "
\n",
+ " \n",
+ " 1601 | \n",
+ " I/B-08.III-C | \n",
+ " [6.57556, 46.12861] | \n",
+ " FR | \n",
+ " NaN | \n",
+ " 1999 | \n",
+ " Pointe de Marcelly | \n",
+ " 440 | \n",
+ " Upper Savoy | \n",
+ " Chablais Alps | \n",
+ "
\n",
+ " \n",
+ " 1602 | \n",
+ " II/C-33.I-B | \n",
+ " [13.36389, 46.53222] | \n",
+ " IT | \n",
+ " NaN | \n",
+ " 1999 | \n",
+ " Monte Scinauz | \n",
+ " 369 | \n",
+ " Udine | \n",
+ " Carnic Alps | \n",
+ "
\n",
+ " \n",
+ " 1603 | \n",
+ " I/A-02.I-E | \n",
+ " [6.59083, 44.02139] | \n",
+ " FR | \n",
+ " NaN | \n",
+ " 1996 | \n",
+ " Puy de Rent | \n",
+ " 556 | \n",
+ " Alpes-de-Haute-Provence | \n",
+ " Maritime Alps | \n",
+ "
\n",
+ " \n",
+ " 1604 | \n",
+ " II/B-23.II-C | \n",
+ " [12.43, 47.47611] | \n",
+ " AT | \n",
+ " NaN | \n",
+ " 1996 | \n",
+ " Kitzbüheler Horn | \n",
+ " 516 | \n",
+ " North Tyrol | \n",
+ " Kitzbühel Alps | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
1605 rows × 9 columns
\n",
+ "
"
+ ]
+ },
+ "execution_count": 83,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 83
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "## Polars\n",
+ "\n",
+ "If you have installed `polars`, you can use a result set’s `.PolarsDataFrame()` method,\n",
+ "see [JupySQL Polars integration].\n",
+ "\n",
+ "[JupySQL Polars integration]: https://jupysql.ploomber.io/en/latest/integrations/polars.html"
+ ],
+ "id": "fe329de76dade158"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-12T23:28:54.362178Z",
+ "start_time": "2025-01-12T23:28:52.201648Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "result = %sql SELECT * FROM sys.summits\n",
+ "df = result.PolarsDataFrame()\n",
+ "df"
+ ],
+ "id": "8d76d75ed3ba81e7",
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Generating CTE with stored snippets: 'summits'"
+ ],
+ "text/html": [
+ "Generating CTE with stored snippets: 'summits'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Running query in 'crate://'"
+ ],
+ "text/html": [
+ "Running query in 'crate://'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "1605 rows affected."
+ ],
+ "text/html": [
+ "1605 rows affected."
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "shape: (1_605, 9)\n",
+ "┌────────────┬───────────┬─────────┬───────────┬───┬───────────┬───────────┬───────────┬───────────┐\n",
+ "│ classifica ┆ coordinat ┆ country ┆ first_asc ┆ … ┆ mountain ┆ prominenc ┆ range ┆ region │\n",
+ "│ tion ┆ es ┆ --- ┆ ent ┆ ┆ --- ┆ e ┆ --- ┆ --- │\n",
+ "│ --- ┆ --- ┆ str ┆ --- ┆ ┆ str ┆ --- ┆ str ┆ str │\n",
+ "│ str ┆ list[f64] ┆ ┆ i64 ┆ ┆ ┆ i64 ┆ ┆ │\n",
+ "╞════════════╪═══════════╪═════════╪═══════════╪═══╪═══════════╪═══════════╪═══════════╪═══════════╡\n",
+ "│ I/B-07.V-B ┆ [6.86444, ┆ FR/IT ┆ 1786 ┆ … ┆ Mont ┆ 4695 ┆ U-Savoy/A ┆ Mont │\n",
+ "│ ┆ 45.8325] ┆ ┆ ┆ ┆ Blanc ┆ ┆ osta ┆ Blanc │\n",
+ "│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ massif │\n",
+ "│ I/B-09.III ┆ [7.86694, ┆ CH ┆ 1855 ┆ … ┆ Monte ┆ 2165 ┆ Valais ┆ Monte │\n",
+ "│ -A ┆ 45.93694] ┆ ┆ ┆ ┆ Rosa ┆ ┆ ┆ Rosa Alps │\n",
+ "│ I/B-09.V-A ┆ [7.85889, ┆ CH ┆ 1858 ┆ … ┆ Dom ┆ 1046 ┆ Valais ┆ Mischabel │\n",
+ "│ ┆ 46.09389] ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n",
+ "│ I/B-09.III ┆ [7.83556, ┆ CH/IT ┆ 1861 ┆ … ┆ Liskamm ┆ 376 ┆ Valais/Ao ┆ Monte │\n",
+ "│ -A ┆ 45.92222] ┆ ┆ ┆ ┆ ┆ ┆ sta ┆ Rosa Alps │\n",
+ "│ I/B-09.II- ┆ [7.71583, ┆ CH ┆ 1861 ┆ … ┆ Weisshorn ┆ 1235 ┆ Valais ┆ Weisshorn │\n",
+ "│ D ┆ 46.10139] ┆ ┆ ┆ ┆ ┆ ┆ ┆ -Matterho │\n",
+ "│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ rn │\n",
+ "│ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … │\n",
+ "│ II/C-29.I- ┆ [9.61583, ┆ IT ┆ null ┆ … ┆ Monte ┆ 471 ┆ Bergamo ┆ Bergamo │\n",
+ "│ B ┆ 45.92972] ┆ ┆ ┆ ┆ Venturosa ┆ ┆ ┆ Alps │\n",
+ "│ I/B-08.III ┆ [6.57556, ┆ FR ┆ null ┆ … ┆ Pointe de ┆ 440 ┆ Upper ┆ Chablais │\n",
+ "│ -C ┆ 46.12861] ┆ ┆ ┆ ┆ Marcelly ┆ ┆ Savoy ┆ Alps │\n",
+ "│ II/C-33.I- ┆ [13.36389 ┆ IT ┆ null ┆ … ┆ Monte ┆ 369 ┆ Udine ┆ Carnic │\n",
+ "│ B ┆ , ┆ ┆ ┆ ┆ Scinauz ┆ ┆ ┆ Alps │\n",
+ "│ ┆ 46.53222] ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n",
+ "│ I/A-02.I-E ┆ [6.59083, ┆ FR ┆ null ┆ … ┆ Puy de ┆ 556 ┆ Alpes-de- ┆ Maritime │\n",
+ "│ ┆ 44.02139] ┆ ┆ ┆ ┆ Rent ┆ ┆ Haute-Pro ┆ Alps │\n",
+ "│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ vence ┆ │\n",
+ "│ II/B-23.II ┆ [12.43, ┆ AT ┆ null ┆ … ┆ Kitzbühel ┆ 516 ┆ North ┆ Kitzbühel │\n",
+ "│ -C ┆ 47.47611] ┆ ┆ ┆ ┆ er Horn ┆ ┆ Tyrol ┆ Alps │\n",
+ "└────────────┴───────────┴─────────┴───────────┴───┴───────────┴───────────┴───────────┴───────────┘"
+ ],
+ "text/html": [
+ "\n",
+ "
shape: (1_605, 9)classification | coordinates | country | first_ascent | height | mountain | prominence | range | region |
---|
str | list[f64] | str | i64 | i64 | str | i64 | str | str |
"I/B-07.V-B" | [6.86444, 45.8325] | "FR/IT" | 1786 | 4808 | "Mont Blanc" | 4695 | "U-Savoy/Aosta" | "Mont Blanc massif" |
"I/B-09.III-A" | [7.86694, 45.93694] | "CH" | 1855 | 4634 | "Monte Rosa" | 2165 | "Valais" | "Monte Rosa Alps" |
"I/B-09.V-A" | [7.85889, 46.09389] | "CH" | 1858 | 4545 | "Dom" | 1046 | "Valais" | "Mischabel" |
"I/B-09.III-A" | [7.83556, 45.92222] | "CH/IT" | 1861 | 4527 | "Liskamm" | 376 | "Valais/Aosta" | "Monte Rosa Alps" |
"I/B-09.II-D" | [7.71583, 46.10139] | "CH" | 1861 | 4506 | "Weisshorn" | 1235 | "Valais" | "Weisshorn-Matterhorn" |
… | … | … | … | … | … | … | … | … |
"II/C-29.I-B" | [9.61583, 45.92972] | "IT" | null | 1999 | "Monte Venturosa" | 471 | "Bergamo" | "Bergamo Alps" |
"I/B-08.III-C" | [6.57556, 46.12861] | "FR" | null | 1999 | "Pointe de Marcelly" | 440 | "Upper Savoy" | "Chablais Alps" |
"II/C-33.I-B" | [13.36389, 46.53222] | "IT" | null | 1999 | "Monte Scinauz" | 369 | "Udine" | "Carnic Alps" |
"I/A-02.I-E" | [6.59083, 44.02139] | "FR" | null | 1996 | "Puy de Rent" | 556 | "Alpes-de-Haute-Provence" | "Maritime Alps" |
"II/B-23.II-C" | [12.43, 47.47611] | "AT" | null | 1996 | "Kitzbüheler Horn" | 516 | "North Tyrol" | "Kitzbühel Alps" |
"
+ ]
+ },
+ "execution_count": 85,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 85
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ ":::{todo}\n",
+ "- ggplot: https://jupysql.ploomber.io/en/latest/user-guide/ggplot.html\n",
+ "- boxplot: https://jupysql.ploomber.io/en/latest/user-guide/argument-expansion.html\n",
+ "- pandas persist: https://jupysql.ploomber.io/en/latest/integrations/pandas.html\n",
+ "- compatibility: https://jupysql.ploomber.io/en/latest/integrations/compatibility.html\n",
+ ":::"
+ ],
+ "id": "580564ba4c478322"
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 2
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython2",
+ "version": "2.7.6"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/notebook/jupyter/pyproject.toml b/notebook/jupyter/pyproject.toml
index e69de29b..2c436b80 100644
--- a/notebook/jupyter/pyproject.toml
+++ b/notebook/jupyter/pyproject.toml
@@ -0,0 +1,226 @@
+[build-system]
+build-backend = "setuptools.build_meta"
+requires = [
+ "setuptools>=42", # At least v42 of setuptools required.
+ "versioningit",
+]
+
+[project]
+name = "cratedb-jupyter-examples"
+description = "CrateDB Jupyter Examples"
+readme = "README.md"
+keywords = [
+ "cratedb",
+ "data-processing",
+ "jupyter",
+]
+license = { text = "Apache 2.0" }
+authors = [
+ { name = "CrateDB Developers" },
+]
+requires-python = ">=3.8"
+classifiers = [
+ "Development Status :: 4 - Beta",
+ "Environment :: Console",
+ "Intended Audience :: Customer Service",
+ "Intended Audience :: Developers",
+ "Intended Audience :: Education",
+ "Intended Audience :: End Users/Desktop",
+ "Intended Audience :: Information Technology",
+ "Intended Audience :: Manufacturing",
+ "Intended Audience :: Science/Research",
+ "Intended Audience :: System Administrators",
+ "Intended Audience :: Telecommunications Industry",
+ "License :: OSI Approved :: Apache Software License",
+ "Operating System :: MacOS :: MacOS X",
+ "Operating System :: Microsoft :: Windows",
+ "Operating System :: POSIX :: Linux",
+ "Operating System :: Unix",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3 :: Only",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
+ "Topic :: Adaptive Technologies",
+ "Topic :: Communications",
+ "Topic :: Database",
+ "Topic :: Documentation",
+ "Topic :: Education",
+ "Topic :: Internet",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Office/Business",
+ "Topic :: Scientific/Engineering",
+ "Topic :: Software Development :: Embedded Systems",
+ "Topic :: Software Development :: Libraries",
+ "Topic :: Software Development :: Object Brokering",
+ "Topic :: Software Development :: Pre-processors",
+ "Topic :: Software Development :: Quality Assurance",
+ "Topic :: Software Development :: Testing",
+ "Topic :: Software Development :: Version Control",
+ "Topic :: System :: Archiving",
+ "Topic :: System :: Benchmark",
+ "Topic :: System :: Clustering",
+ "Topic :: System :: Distributed Computing",
+ "Topic :: System :: Hardware",
+ "Topic :: System :: Logging",
+ "Topic :: System :: Monitoring",
+ "Topic :: System :: Networking",
+ "Topic :: System :: Systems Administration",
+ "Topic :: Text Processing",
+ "Topic :: Utilities",
+]
+dynamic = [
+ "version",
+]
+dependencies = [
+ "csvkit<3",
+ "jupysql<0.11",
+ "matplotlib<4",
+ "pandas<3",
+ "polars<2",
+ "sqlalchemy-cratedb>=0.40,<1",
+ "toml<0.11",
+]
+optional-dependencies.all = [
+ "cratedb-jupyter-examples[docs,develop,test]",
+]
+optional-dependencies.develop = [
+ "poethepoet<0.33",
+ "pyproject-fmt<2.6",
+ "ruff<0.9",
+ "validate-pyproject<0.24",
+]
+optional-dependencies.docs = [
+ "furo",
+ "myst-nb<1.2",
+ "sphinx-autobuild==2021.3.14", # Newer versions stopped "watching" appropriately?
+ "sphinx-copybutton",
+ "sphinx-design-elements<1",
+]
+optional-dependencies.test = [
+ "pueblo[notebook,testing]",
+]
+
+urls.Homepage = "https://github.com/crate/cratedb-examples/tree/main/notebook/jupyter"
+urls.Issues = "https://github.com/crate/cratedb-examples/issues"
+urls.Repository = "https://github.com/crate/cratedb-examples"
+
+[tool.setuptools.packages.find]
+namespaces = false
+
+# ===================
+# Tasks configuration
+# ===================
+
+[tool.ruff]
+line-length = 100
+
+lint.select = [
+ # Builtins
+ "A",
+ # Bugbear
+ "B",
+ # comprehensions
+ "C4",
+ # Pycodestyle
+ "E",
+ # eradicate
+ "ERA",
+ # Pyflakes
+ "F",
+ # isort
+ "I",
+ # pandas-vet
+ "PD",
+ # return
+ "RET",
+ # Bandit
+ "S",
+ # print
+ "T20",
+ "W",
+ # flake8-2020
+ "YTT",
+]
+
+lint.extend-ignore = [
+ # zip() without an explicit strict= parameter
+ "B905",
+ # df is a bad variable name. Be kinder to your future self.
+ "PD901",
+ # Unnecessary variable assignment before `return` statement
+ "RET504",
+ # Unnecessary `elif` after `return` statement
+ "RET505",
+ # Probable insecure usage of temporary file or directory
+ "S108",
+]
+
+lint.per-file-ignores."docs/conf.py" = [ "A001", "ERA001" ]
+
+[tool.pytest.ini_options]
+minversion = "2.0"
+addopts = """
+ -rfEX -p pytester --strict-markers --verbosity=3
+ --cov --cov-report=term-missing
+ --capture=no
+ """
+env = [
+ "CRATEDB_CONNECTION_STRING=crate://crate@localhost/?schema=notebook",
+]
+log_level = "DEBUG"
+log_cli_level = "DEBUG"
+testpaths = [ "*.py" ]
+xfail_strict = true
+markers = [
+]
+
+[tool.coverage.run]
+branch = false
+omit = [
+ "test*",
+]
+
+[tool.coverage.report]
+fail_under = 0
+show_missing = true
+
+[tool.versioningit.vcs]
+method = "git"
+default-tag = "0.0.0"
+
+[tool.poe.tasks]
+
+check = [
+ "lint",
+ "test",
+]
+
+docs-html = [
+ { cmd = "sphinx-build -W --keep-going -b html ./docs ./_build/html" },
+]
+docs-linkcheck = [
+ { cmd = "sphinx-build -W --keep-going -b linkcheck ./docs ./_build/html" },
+]
+
+format = [
+ # Format project metadata.
+ { cmd = "pyproject-fmt --keep-full-version pyproject.toml" },
+
+ # Format code.
+ # Configure Ruff not to auto-fix a few items that are useful in workbench mode.
+ # e.g.: unused imports (F401), unused variables (F841), `print` statements (T201), commented-out code (ERA001)
+ { cmd = "ruff format" },
+ { cmd = "ruff check --fix --ignore=ERA --ignore=F401 --ignore=F841 --ignore=T20 --ignore=ERA001" },
+]
+
+lint = [
+ { cmd = "ruff format --check" },
+ { cmd = "ruff check" },
+ { cmd = "validate-pyproject pyproject.toml" },
+]
+
+test = { cmd = "pytest" }
diff --git a/notebook/jupyter/test.py b/notebook/jupyter/test.py
new file mode 100644
index 00000000..91067bd9
--- /dev/null
+++ b/notebook/jupyter/test.py
@@ -0,0 +1,9 @@
+from testbook import testbook
+
+
+def test_notebook(notebook):
+ """
+ Execute Jupyter Notebook, one test case per .ipynb file.
+ """
+ with testbook(notebook) as tb:
+ tb.execute()