This is an automated email from the ASF dual-hosted git repository.

Jackie-Jiang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new 810d7985b7e Add Jupyter JupySQL tutorial for querying Pinot (#19374)
810d7985b7e is described below

commit 810d7985b7ec485a455d443310a7bce83205ebd5
Author: Sruthi <[email protected]>
AuthorDate: Mon Sep 14 18:36:29 2026 -0700

    Add Jupyter JupySQL tutorial for querying Pinot (#19374)
---
 contrib/jupyter-jupysql/README.md               |  76 +++++++++
 contrib/jupyter-jupysql/pinot_jupysql_eda.ipynb | 215 ++++++++++++++++++++++++
 contrib/jupyter-jupysql/requirements.txt        |  25 +++
 3 files changed, 316 insertions(+)

diff --git a/contrib/jupyter-jupysql/README.md 
b/contrib/jupyter-jupysql/README.md
new file mode 100644
index 00000000000..1626b799bb2
--- /dev/null
+++ b/contrib/jupyter-jupysql/README.md
@@ -0,0 +1,76 @@
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+
+# Query Pinot from Jupyter with JupySQL
+
+This example shows how to query Apache Pinot from a Jupyter notebook using
+[JupySQL](https://jupysql.ploomber.io/) and the 
[pinotdb](https://pypi.org/project/pinotdb/)
+Python client. It is meant for local EDA: SQL magics, pandas DataFrames, and 
simple plots.
+
+Related issue: https://github.com/apache/pinot/issues/10160
+
+## Prerequisites
+
+A running batch quickstart (loads the `baseballStats` table). The broker SQL
+endpoint is **port 8000** (not 8099, which appears in some older client 
snippets).
+The controller UI is port 9000.
+
+### Option A — local binary (this checkout)
+
+From the Pinot repo root, after `./mvnw clean install -DskipTests -Pbin-dist`:
+
+```bash
+./build/bin/quick-start-batch.sh
+```
+
+### Option B — Docker
+
+```bash
+docker run --name pinot-quickstart \
+  -p 2123:2123 -p 9000:9000 -p 8000:8000 \
+  -d apachepinot/pinot:latest QuickStart -type batch
+```
+
+Wait until the controller UI at http://localhost:9000 is up.
+
+## Run the notebook
+
+```bash
+cd contrib/jupyter-jupysql
+python3 -m venv .venv
+source .venv/bin/activate
+pip install -r requirements.txt
+jupyter notebook pinot_jupysql_eda.ipynb
+```
+
+Connection string used in the notebook (broker **8000**, controller **9000**).
+The engine is created with `use_multistage_engine=true` so JupySQL `%sqlplot`
+CTEs are accepted:
+
+```text
+pinot://localhost:8000/query/sql?controller=http://localhost:9000/
+```
+
+To execute all cells headlessly (quickstart must already be running):
+
+```bash
+jupyter nbconvert --to notebook --execute pinot_jupysql_eda.ipynb --output 
pinot_jupysql_eda.executed.ipynb
+```
diff --git a/contrib/jupyter-jupysql/pinot_jupysql_eda.ipynb 
b/contrib/jupyter-jupysql/pinot_jupysql_eda.ipynb
new file mode 100644
index 00000000000..34a2c97b0b4
--- /dev/null
+++ b/contrib/jupyter-jupysql/pinot_jupysql_eda.ipynb
@@ -0,0 +1,215 @@
+{
+  "cells": [
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "Licensed to the Apache Software Foundation (ASF) under one\n",
+        "or more contributor license agreements.  See the NOTICE file\n",
+        "distributed with this work for additional information\n",
+        "regarding copyright ownership.  The ASF licenses this file\n",
+        "to you under the Apache License, Version 2.0 (the\n",
+        "\"License\"); you may not use this file except in compliance\n",
+        "with the License.  You may obtain a copy of the License at\n",
+        "\n",
+        "  http://www.apache.org/licenses/LICENSE-2.0\n";,
+        "\n",
+        "Unless required by applicable law or agreed to in writing,\n",
+        "software distributed under the License is distributed on an\n",
+        "\"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n",
+        "KIND, either express or implied.  See the License for the\n",
+        "specific language governing permissions and limitations\n",
+        "under the License."
+      ],
+      "id": "license"
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "# Query Apache Pinot from Jupyter with JupySQL\n",
+        "\n",
+        "This notebook queries a local Pinot **batch quickstart** from Jupyter 
using\n",
+        "[JupySQL](https://jupysql.ploomber.io/) SQL magics and 
[pinotdb](https://pypi.org/project/pinotdb/).\n",
+        "\n",
+        "It covers:\n",
+        "\n",
+        "1. Connecting to Pinot from a notebook\n",
+        "2. Running SQL (`SELECT`, `GROUP BY`, `ORDER BY`)\n",
+        "3. Plotting query results\n",
+        "4. Keeping results as a pandas DataFrame for later EDA or modeling\n",
+        "\n",
+        "**Start Pinot first** (broker on port **8000**, controller on 
**9000**):\n",
+        "\n",
+        "```bash\n",
+        "./build/bin/quick-start-batch.sh\n",
+        "```\n",
+        "\n",
+        "or\n",
+        "\n",
+        "```bash\n",
+        "docker run --name pinot-quickstart -p 2123:2123 -p 9000:9000 -p 
8000:8000 -d apachepinot/pinot:latest QuickStart -type batch\n",
+        "```\n",
+        "\n",
+        "The quickstart loads `baseballStats`. See `README.md` in this 
directory for install steps."
+      ],
+      "id": "intro"
+    },
+    {
+      "cell_type": "code",
+      "metadata": {},
+      "source": [
+        "from sqlalchemy import create_engine\n",
+        "import matplotlib.pyplot as plt\n",
+        "\n",
+        "%matplotlib inline\n",
+        "%load_ext sql\n",
+        "\n",
+        "%config SqlMagic.autopandas = True\n",
+        "%config SqlMagic.feedback = False\n",
+        "%config SqlMagic.displaycon = False\n",
+        "\n",
+        "# Batch / Docker quickstart: broker 8000, controller 9000 (not 
8099).\n",
+        "# Multi-stage is required for JupySQL %sqlplot, which rewrites plots 
as CTEs.\n",
+        "engine = create_engine(\n",
+        "    
\"pinot://localhost:8000/query/sql?controller=http://localhost:9000/\",\n";,
+        "    connect_args={\"use_multistage_engine\": \"true\"},\n",
+        ")\n",
+        "%sql engine"
+      ],
+      "execution_count": null,
+      "outputs": [],
+      "id": "imports"
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "## Query Pinot with SQL magics\n",
+        "\n",
+        "`%%sql` sends the statement to the Pinot broker (`POST 
/query/sql`).\n",
+        "Use `LIMIT` on exploratory scans. Aggregations on `baseballStats` are 
cheap."
+      ],
+      "id": "query-sql"
+    },
+    {
+      "cell_type": "code",
+      "metadata": {},
+      "source": [
+        "%%sql\n",
+        "SELECT playerName, teamID, yearID, runs, homeRuns\n",
+        "FROM baseballStats\n",
+        "LIMIT 5"
+      ],
+      "execution_count": null,
+      "outputs": [],
+      "id": "select-limit"
+    },
+    {
+      "cell_type": "code",
+      "metadata": {},
+      "source": [
+        "%%sql\n",
+        "SELECT playerName, SUM(runs) AS sum_runs\n",
+        "FROM baseballStats\n",
+        "WHERE yearID >= 2000\n",
+        "GROUP BY playerName\n",
+        "ORDER BY sum_runs DESC\n",
+        "LIMIT 10"
+      ],
+      "execution_count": null,
+      "outputs": [],
+      "id": "group-by"
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "## Plot query results\n",
+        "\n",
+        "Assign a `%sql` result to a variable. With `SqlMagic.autopandas = 
True` you get a\n",
+        "DataFrame you can plot with matplotlib (or pass into `%sqlplot`)."
+      ],
+      "id": "plot-md"
+    },
+    {
+      "cell_type": "code",
+      "metadata": {},
+      "source": [
+        "top_teams = %sql SELECT teamID, SUM(runs) AS total_runs FROM 
baseballStats GROUP BY teamID ORDER BY total_runs DESC LIMIT 10\n",
+        "\n",
+        "ax = top_teams.plot.bar(x=\"teamID\", y=\"total_runs\", 
legend=False)\n",
+        "ax.set_title(\"Top 10 teams by total runs (baseballStats)\")\n",
+        "ax.set_xlabel(\"teamID\")\n",
+        "ax.set_ylabel(\"total runs\")\n",
+        "plt.tight_layout()\n",
+        "plt.show()"
+      ],
+      "execution_count": null,
+      "outputs": [],
+      "id": "plot-code"
+    },
+    {
+      "cell_type": "code",
+      "metadata": {},
+      "source": [
+        "%%sql --save top_teams_sql\n",
+        "SELECT teamID, SUM(runs) AS total_runs\n",
+        "FROM baseballStats\n",
+        "GROUP BY teamID\n",
+        "ORDER BY total_runs DESC\n",
+        "LIMIT 10"
+      ],
+      "execution_count": null,
+      "outputs": [],
+      "id": "sqlplot"
+    },
+    {
+      "cell_type": "code",
+      "metadata": {},
+      "source": [
+        "%sqlplot bar --table top_teams_sql --column teamID total_runs"
+      ],
+      "execution_count": null,
+      "outputs": [],
+      "id": "sqlplot-bar"
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "## Keep results for EDA or modeling\n",
+        "\n",
+        "The DataFrame is a normal pandas object. Use it for further EDA or as 
features\n",
+        "for a model — training a model is out of scope for this tutorial."
+      ],
+      "id": "eda-md"
+    },
+    {
+      "cell_type": "code",
+      "metadata": {},
+      "source": [
+        "player_runs = %sql SELECT playerName, SUM(runs) AS sum_runs, 
SUM(homeRuns) AS sum_hr FROM baseballStats WHERE yearID >= 2000 GROUP BY 
playerName ORDER BY sum_runs DESC LIMIT 20\n",
+        "\n",
+        "print(player_runs.dtypes)\n",
+        "player_runs.head()"
+      ],
+      "execution_count": null,
+      "outputs": [],
+      "id": "eda-code"
+    }
+  ],
+  "metadata": {
+    "kernelspec": {
+      "display_name": "Python 3",
+      "language": "python",
+      "name": "python3"
+    },
+    "language_info": {
+      "name": "python",
+      "pygments_lexer": "ipython3"
+    }
+  },
+  "nbformat": 4,
+  "nbformat_minor": 5
+}
\ No newline at end of file
diff --git a/contrib/jupyter-jupysql/requirements.txt 
b/contrib/jupyter-jupysql/requirements.txt
new file mode 100644
index 00000000000..06d783b68de
--- /dev/null
+++ b/contrib/jupyter-jupysql/requirements.txt
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+pinotdb>=9.1.0
+jupysql>=0.10.0
+pandas>=2.0
+matplotlib>=3.8
+jupyter>=1.0
+sqlalchemy>=2.0
+ipykernel>=6.0
+nbconvert>=7.0


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to