{
"cells": [
{
"cell_type": "markdown",
"id": "e71caf17-6e95-45ec-b839-edb6b2384a47",
"metadata": {},
"source": [
"# Microsoft SQL Server\n",
"\n",
"In this tutorial, we'll see how to query Microsoft SQL Server from Jupyter. Optionally, you can spin up a testing server.\n",
"\n",
"```{tip}\n",
"If you encounter issues, feel free to join our [community](https://ploomber.io/community) and we'll be happy to help!\n",
"```\n",
"\n",
"## Pre-requisites\n",
"\n",
"The first step is to install the [ODBC driver for SQL Server](https://learn.microsoft.com/en-us/sql/connect/odbc/microsoft-odbc-driver-for-sql-server?view=sql-server-ver16).\n",
"\n",
"- Instructions for [Linux](https://learn.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-ver16&tabs=alpine18-install%2Calpine17-install%2Cdebian8-install%2Credhat7-13-install%2Crhel7-offline)\n",
"- Instructions for [Mac](https://learn.microsoft.com/en-us/sql/connect/odbc/linux-mac/install-microsoft-odbc-driver-sql-server-macos?view=sql-server-ver16)\n",
"\n",
"For example, if you're on a Mac, you can install the driver with `brew`:\n",
"\n",
"```sh\n",
"/bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)\"\n",
"brew tap microsoft/mssql-release https://github.com/Microsoft/homebrew-mssql-release\n",
"brew update\n",
"HOMEBREW_ACCEPT_EULA=Y brew install msodbcsql18 mssql-tools18\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "459d2fb2-82e8-4fff-84b7-f303f4afb4d5",
"metadata": {},
"source": [
"## Start Microsoft SQL Server instance\n",
"\n",
"If you don't have a SQL Server running or you want to spin up one for testing, you can do it with the official [Docker image](https://hub.docker.com/_/microsoft-mssql-server).\n",
"\n",
"```{important}\n",
"If you're on a Mac with Apple Silicon (e.g., M1 processor), ensure you're running the latest Docker Desktop version. More info [here](https://bornsql.ca/blog/you-can-run-a-sql-server-docker-container-on-apple-m1-and-m2-silicon/).\n",
"```\n",
"\n",
"\n",
"To start the server:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "332eec37-b2b2-4a3b-98ec-138361752f6e",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"00721df70ea8d5f91c792a84f28f4e0fc6c0ff53f1f4d04cb6911a3a4714deba\n"
]
}
],
"source": [
"%%bash\n",
"docker run -e \"ACCEPT_EULA=Y\" \\\n",
" -e \"MSSQL_SA_PASSWORD=MyPassword!\" \\\n",
" -p 1433:1433 \\\n",
" -d mcr.microsoft.com/mssql/server:2022-latest"
]
},
{
"cell_type": "markdown",
"id": "7d18492d-e58c-4117-ac5d-602bc7e6445c",
"metadata": {},
"source": [
"```{important}\n",
"Ensure you set a strong password, otherwise the container will shut down silently!\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "7f420cee-848e-443e-997e-a6066d5fe704",
"metadata": {},
"source": [
"Ensure that your container is running (run the command a few seconds after running the previous one to ensure it dind't shut down silently):"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "4cf0cbb5-a120-4b5c-8a49-8484ba5c01fa",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES\n"
]
}
],
"source": [
"%%bash\n",
"docker ps"
]
},
{
"cell_type": "markdown",
"id": "a8cc1b60-5d0e-45f6-bb8c-b68952a39f62",
"metadata": {},
"source": [
"If you have issues with the previous command, you can try with SQL Edge:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "83bff03a-cae7-4775-a2b6-d85ceb0ce440",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"fabfc30490a17dc0a48313c35289218ff563070b11622bc43f07e82080b2a201\n"
]
}
],
"source": [
"%%bash\n",
"docker run -e \"ACCEPT_EULA=1\" -e \"MSSQL_SA_PASSWORD=MyPassword!\" \\\n",
" -e \"MSSQL_PID=Developer\" -e \"MSSQL_USER=sa\" \\\n",
" -p 1433:1433 -d --name=sql mcr.microsoft.com/azure-sql-edge"
]
},
{
"cell_type": "markdown",
"id": "489d3952-e5a8-4c11-9341-b06fef290d4d",
"metadata": {},
"source": [
"Ensure the server is running (wait for a few seconds before running it):"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "f8406efe-2939-4511-b107-daff097f3d54",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES\n",
"fabfc30490a1 mcr.microsoft.com/azure-sql-edge \"/opt/mssql/bin/perm…\" 5 seconds ago Up 4 seconds 1401/tcp, 0.0.0.0:1433->1433/tcp sql\n"
]
}
],
"source": [
"%%bash\n",
"docker ps"
]
},
{
"cell_type": "markdown",
"id": "41ed6732-6bda-4417-ae4e-8bc964196c8f",
"metadata": {},
"source": [
"## Installing `pyodbc`\n",
"\n",
"\n",
"`pyodbc` will allow us to connect to SQL Server. If you're on macOS or Linux, you need to install unixODBC. Note that when installing the ODBC driver on macOS using `brew`, unixODBC is also installed.\n",
"\n",
"\n",
"Install `pyodbc` with:\n",
"\n",
"```sh\n",
"pip install pyodbc\n",
"```\n",
"\n",
"```{note}\n",
"If you're on a Mac with Apple Silicon (e.g., M1 processor), you might encounter issues, if so, try thi:\n",
"\n",
"~~~sh\n",
"pip install pyodbc==4.0.34\n",
"~~~\n",
"```\n",
"\n",
"Verify a successful installation with:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "27a84060-f8f1-406c-b206-898c4975809f",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"import pyodbc"
]
},
{
"cell_type": "markdown",
"id": "3c2fe1cf-cedc-48ea-a420-36c5c0c24980",
"metadata": {},
"source": [
"Verify that `pyodbc` is able to findn the SQL Server driver:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "c7d03c98-9cc3-4c56-a349-0e4f146115d9",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/plain": [
"['ODBC Driver 18 for SQL Server']"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"pyodbc.drivers()"
]
},
{
"cell_type": "markdown",
"id": "45386e02-c70a-44a0-9635-55e1bcdbdfc7",
"metadata": {},
"source": [
"```{tip}\n",
"If the driver doesn't appear, uninstalling `pyodbc` and re-installing it again might fix the problem.\n",
"\n",
"If you're on a Mac with Apple Silicon, ensure you installed `pyodbc` with `pip`, since `conda` might lead to issues.\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "1ad9f206-5dee-41ec-97a3-8b2622b6b433",
"metadata": {},
"source": [
"## Starting the connection\n",
"\n",
"To start the connection, execute the following, change the values to match your SQL Server's configurationo:"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "b2f987d7-c60b-480c-b31c-cd2f6562c9b8",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"from sqlalchemy import create_engine\n",
"from sqlalchemy.engine import URL\n",
"\n",
"connection_url = URL.create(\n",
" \"mssql+pyodbc\",\n",
" username=\"sa\",\n",
" password=\"MyPassword!\",\n",
" host=\"localhost\",\n",
" port=1433,\n",
" database=\"master\",\n",
" query={\n",
" \"driver\": \"ODBC Driver 18 for SQL Server\",\n",
" \"Encrypt\": \"yes\",\n",
" \"TrustServerCertificate\": \"yes\",\n",
" },\n",
")\n",
"engine = create_engine(connection_url)"
]
},
{
"cell_type": "markdown",
"id": "8f45a6f0-a9e3-4282-843f-44169b45e4c2",
"metadata": {
"user_expressions": []
},
"source": [
"```{note}\n",
"If using `pytds`, the `autocommit` feature is disabled since it's not compatible with JupySQL.\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "354906eb-5b76-44dc-9568-c7cda37ccfbc",
"metadata": {},
"source": [
"Install, load the Jupyter extension and start the connection:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "d87fd635-8914-4e4e-9461-405f5ec7d581",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Note: you may need to restart the kernel to use updated packages.\n"
]
}
],
"source": [
"%pip install jupysql --quiet"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "f62bf48d-3e7b-4d5f-99b5-4d48fce98dfc",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/html": [
"Found pyproject.toml from '/Users/eduardo/dev/jupysql'"
],
"text/plain": [
"Found pyproject.toml from '/Users/eduardo/dev/jupysql'"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"%load_ext sql\n",
"%sql engine"
]
},
{
"cell_type": "markdown",
"id": "c92746b7-85d1-421b-9e70-fd7c8e4930d6",
"metadata": {},
"source": [
"```{note}\n",
"\n",
"If you see the following error:\n",
"\n",
"~~~\n",
"InterfaceError: (pyodbc.InterfaceError) ('IM002', '[IM002] [unixODBC][Driver Manager]Data source name not found and no default driver specified (0) (SQLDriverConnect)')\n",
"(Background on this error at: https://sqlalche.me/e/14/rvf5)\n",
"~~~\n",
"\n",
"It might be that you're missing the SQL Server ODBC driver or that `pyodbc` cannot find it.\n",
"\n",
"```\n",
"\n",
"\n",
"## Load sample data\n",
"\n",
"Let's upload some sample data:"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "aea381e9-9c61-4c15-bd09-aecb87a52e74",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/plain": [
"(1369769, 19)"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import pandas as pd\n",
"\n",
"df = pd.read_parquet(\n",
" \"https://d37ci6vzurychx.cloudfront.net/trip-data/yellow_tripdata_2021-01.parquet\"\n",
")\n",
"df.shape"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "f1b4ce63-5a6c-4887-91d8-7457f08b53ed",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/plain": [
"56"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df.sample(100_000).to_sql(\n",
" name=\"taxi\", con=engine, chunksize=100_000, if_exists=\"replace\"\n",
")"
]
},
{
"cell_type": "markdown",
"id": "2e6183c7",
"metadata": {},
"source": [
"## Query"
]
},
{
"cell_type": "markdown",
"id": "8ee94760-27bb-40db-9bc7-e7d5a9ec585b",
"metadata": {},
"source": [
"Query the new table:"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "b4dfc187-015f-4394-b3d9-f9fc0a79c4e5",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/html": [
"Running query in 'mssql+pyodbc://sa:***@localhost:1433/master?Encrypt=yes&TrustServerCertificate=yes&driver=ODBC+Driver+18+for+SQL+Server'"
],
"text/plain": [
"Running query in 'mssql+pyodbc://sa:***@localhost:1433/master?Encrypt=yes&TrustServerCertificate=yes&driver=ODBC+Driver+18+for+SQL+Server'"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"
\n",
" \n",
" \n",
" | \n",
"
\n",
" \n",
" \n",
" \n",
" 100000 | \n",
"
\n",
" \n",
"
\n",
"ResultSet
: to convert to pandas, call .DataFrame()
or to polars, call .PolarsDataFrame()
"
],
"text/plain": [
"+--------+\n",
"| |\n",
"+--------+\n",
"| 100000 |\n",
"+--------+"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%%sql\n",
"select COUNT(*) FROM taxi"
]
},
{
"cell_type": "markdown",
"id": "e134d0ed-5501-4425-bd97-302b07062d57",
"metadata": {},
"source": [
"List the tables in the database:"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "cad65f83-48d5-415e-aaeb-8fa7b4e2256c",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/html": [
"\n",
" \n",
" \n",
" Name | \n",
"
\n",
" \n",
" \n",
" \n",
" MSreplication_options | \n",
"
\n",
" \n",
" spt_fallback_db | \n",
"
\n",
" \n",
" spt_fallback_dev | \n",
"
\n",
" \n",
" spt_fallback_usg | \n",
"
\n",
" \n",
" spt_monitor | \n",
"
\n",
" \n",
" taxi | \n",
"
\n",
" \n",
"
"
],
"text/plain": [
"+-----------------------+\n",
"| Name |\n",
"+-----------------------+\n",
"| MSreplication_options |\n",
"| spt_fallback_db |\n",
"| spt_fallback_dev |\n",
"| spt_fallback_usg |\n",
"| spt_monitor |\n",
"| taxi |\n",
"+-----------------------+"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%sqlcmd tables"
]
},
{
"cell_type": "markdown",
"id": "7af2e627-9089-4381-9543-d923832a2dab",
"metadata": {},
"source": [
"List columns in the taxi table:"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "aa32ce39-8314-4836-919b-0cc9259c44d5",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/html": [
"\n",
" \n",
" \n",
" name | \n",
" type | \n",
" nullable | \n",
" default | \n",
" autoincrement | \n",
" comment | \n",
"
\n",
" \n",
" \n",
" \n",
" index | \n",
" BIGINT | \n",
" True | \n",
" None | \n",
" False | \n",
" None | \n",
"
\n",
" \n",
" VendorID | \n",
" BIGINT | \n",
" True | \n",
" None | \n",
" False | \n",
" None | \n",
"
\n",
" \n",
" tpep_pickup_datetime | \n",
" DATETIME | \n",
" True | \n",
" None | \n",
" False | \n",
" None | \n",
"
\n",
" \n",
" tpep_dropoff_datetime | \n",
" DATETIME | \n",
" True | \n",
" None | \n",
" False | \n",
" None | \n",
"
\n",
" \n",
" passenger_count | \n",
" FLOAT | \n",
" True | \n",
" None | \n",
" False | \n",
" None | \n",
"
\n",
" \n",
" trip_distance | \n",
" FLOAT | \n",
" True | \n",
" None | \n",
" False | \n",
" None | \n",
"
\n",
" \n",
" RatecodeID | \n",
" FLOAT | \n",
" True | \n",
" None | \n",
" False | \n",
" None | \n",
"
\n",
" \n",
" store_and_fwd_flag | \n",
" VARCHAR COLLATE "SQL_Latin1_General_CP1_CI_AS" | \n",
" True | \n",
" None | \n",
" False | \n",
" None | \n",
"
\n",
" \n",
" PULocationID | \n",
" BIGINT | \n",
" True | \n",
" None | \n",
" False | \n",
" None | \n",
"
\n",
" \n",
" DOLocationID | \n",
" BIGINT | \n",
" True | \n",
" None | \n",
" False | \n",
" None | \n",
"
\n",
" \n",
" payment_type | \n",
" BIGINT | \n",
" True | \n",
" None | \n",
" False | \n",
" None | \n",
"
\n",
" \n",
" fare_amount | \n",
" FLOAT | \n",
" True | \n",
" None | \n",
" False | \n",
" None | \n",
"
\n",
" \n",
" extra | \n",
" FLOAT | \n",
" True | \n",
" None | \n",
" False | \n",
" None | \n",
"
\n",
" \n",
" mta_tax | \n",
" FLOAT | \n",
" True | \n",
" None | \n",
" False | \n",
" None | \n",
"
\n",
" \n",
" tip_amount | \n",
" FLOAT | \n",
" True | \n",
" None | \n",
" False | \n",
" None | \n",
"
\n",
" \n",
" tolls_amount | \n",
" FLOAT | \n",
" True | \n",
" None | \n",
" False | \n",
" None | \n",
"
\n",
" \n",
" improvement_surcharge | \n",
" FLOAT | \n",
" True | \n",
" None | \n",
" False | \n",
" None | \n",
"
\n",
" \n",
" total_amount | \n",
" FLOAT | \n",
" True | \n",
" None | \n",
" False | \n",
" None | \n",
"
\n",
" \n",
" congestion_surcharge | \n",
" FLOAT | \n",
" True | \n",
" None | \n",
" False | \n",
" None | \n",
"
\n",
" \n",
" airport_fee | \n",
" FLOAT | \n",
" True | \n",
" None | \n",
" False | \n",
" None | \n",
"
\n",
" \n",
"
"
],
"text/plain": [
"+-----------------------+------------------------------------------------+----------+---------+---------------+---------+\n",
"| name | type | nullable | default | autoincrement | comment |\n",
"+-----------------------+------------------------------------------------+----------+---------+---------------+---------+\n",
"| index | BIGINT | True | None | False | None |\n",
"| VendorID | BIGINT | True | None | False | None |\n",
"| tpep_pickup_datetime | DATETIME | True | None | False | None |\n",
"| tpep_dropoff_datetime | DATETIME | True | None | False | None |\n",
"| passenger_count | FLOAT | True | None | False | None |\n",
"| trip_distance | FLOAT | True | None | False | None |\n",
"| RatecodeID | FLOAT | True | None | False | None |\n",
"| store_and_fwd_flag | VARCHAR COLLATE \"SQL_Latin1_General_CP1_CI_AS\" | True | None | False | None |\n",
"| PULocationID | BIGINT | True | None | False | None |\n",
"| DOLocationID | BIGINT | True | None | False | None |\n",
"| payment_type | BIGINT | True | None | False | None |\n",
"| fare_amount | FLOAT | True | None | False | None |\n",
"| extra | FLOAT | True | None | False | None |\n",
"| mta_tax | FLOAT | True | None | False | None |\n",
"| tip_amount | FLOAT | True | None | False | None |\n",
"| tolls_amount | FLOAT | True | None | False | None |\n",
"| improvement_surcharge | FLOAT | True | None | False | None |\n",
"| total_amount | FLOAT | True | None | False | None |\n",
"| congestion_surcharge | FLOAT | True | None | False | None |\n",
"| airport_fee | FLOAT | True | None | False | None |\n",
"+-----------------------+------------------------------------------------+----------+---------+---------------+---------+"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%sqlcmd columns --table taxi"
]
},
{
"cell_type": "markdown",
"id": "7a3316e7-8ba6-46ac-a46b-6fb3a0d4776c",
"metadata": {},
"source": [
"## Parametrize queries"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "91e78151-7ae2-498d-8a19-3de0ee4781c7",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"threshold = 10"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "7947bc67-9170-4808-835d-b0bf82229022",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/html": [
"Running query in 'mssql+pyodbc://sa:***@localhost:1433/master?Encrypt=yes&TrustServerCertificate=yes&driver=ODBC+Driver+18+for+SQL+Server'"
],
"text/plain": [
"Running query in 'mssql+pyodbc://sa:***@localhost:1433/master?Encrypt=yes&TrustServerCertificate=yes&driver=ODBC+Driver+18+for+SQL+Server'"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"\n",
" \n",
" \n",
" | \n",
"
\n",
" \n",
" \n",
" \n",
" 94705 | \n",
"
\n",
" \n",
"
\n",
"ResultSet
: to convert to pandas, call .DataFrame()
or to polars, call .PolarsDataFrame()
"
],
"text/plain": [
"+-------+\n",
"| |\n",
"+-------+\n",
"| 94705 |\n",
"+-------+"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%%sql\n",
"SELECT COUNT(*) FROM taxi\n",
"WHERE trip_distance < {{threshold}}"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "73acb44a-cb1a-44ab-bfd6-42309fc1defd",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"threshold = 0.5"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "d88db9b4-1efe-4b7b-b8be-f94949b3ce69",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/html": [
"Running query in 'mssql+pyodbc://sa:***@localhost:1433/master?Encrypt=yes&TrustServerCertificate=yes&driver=ODBC+Driver+18+for+SQL+Server'"
],
"text/plain": [
"Running query in 'mssql+pyodbc://sa:***@localhost:1433/master?Encrypt=yes&TrustServerCertificate=yes&driver=ODBC+Driver+18+for+SQL+Server'"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"\n",
" \n",
" \n",
" | \n",
"
\n",
" \n",
" \n",
" \n",
" 5326 | \n",
"
\n",
" \n",
"
\n",
"ResultSet
: to convert to pandas, call .DataFrame()
or to polars, call .PolarsDataFrame()
"
],
"text/plain": [
"+------+\n",
"| |\n",
"+------+\n",
"| 5326 |\n",
"+------+"
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%%sql\n",
"SELECT COUNT(*) FROM taxi\n",
"WHERE trip_distance < {{threshold}}"
]
},
{
"cell_type": "markdown",
"id": "bfb89b06-c5c1-4ad3-bc4f-2f12071c559c",
"metadata": {},
"source": [
"## CTEs\n",
"\n",
"You can break down queries into multiple cells, JupySQL will build a CTE for you:"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "2519b021-90bb-42f4-b637-7dc4e214eaad",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/html": [
"Running query in 'mssql+pyodbc://sa:***@localhost:1433/master?Encrypt=yes&TrustServerCertificate=yes&driver=ODBC+Driver+18+for+SQL+Server'"
],
"text/plain": [
"Running query in 'mssql+pyodbc://sa:***@localhost:1433/master?Encrypt=yes&TrustServerCertificate=yes&driver=ODBC+Driver+18+for+SQL+Server'"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"Skipping execution..."
],
"text/plain": [
"Skipping execution..."
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"%%sql --save many_passengers --no-execute\n",
"SELECT *\n",
"FROM taxi\n",
"WHERE passenger_count > 3\n",
"-- remove top 1% outliers for better visualization\n",
"AND trip_distance < 18.93"
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "856de699-a460-43de-8ea2-d50ea4459340",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/html": [
"Running query in 'mssql+pyodbc://sa:***@localhost:1433/master?Encrypt=yes&TrustServerCertificate=yes&driver=ODBC+Driver+18+for+SQL+Server'"
],
"text/plain": [
"Running query in 'mssql+pyodbc://sa:***@localhost:1433/master?Encrypt=yes&TrustServerCertificate=yes&driver=ODBC+Driver+18+for+SQL+Server'"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"\n",
" \n",
" \n",
" | \n",
" _1 | \n",
" _2 | \n",
"
\n",
" \n",
" \n",
" \n",
" 0.0 | \n",
" 2.5377720207253898 | \n",
" 18.83 | \n",
"
\n",
" \n",
"
\n",
"ResultSet
: to convert to pandas, call .DataFrame()
or to polars, call .PolarsDataFrame()
"
],
"text/plain": [
"+-----+--------------------+-------+\n",
"| | _1 | _2 |\n",
"+-----+--------------------+-------+\n",
"| 0.0 | 2.5377720207253898 | 18.83 |\n",
"+-----+--------------------+-------+"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%%sql --save trip_stats --with many_passengers\n",
"SELECT MIN(trip_distance), AVG(trip_distance), MAX(trip_distance)\n",
"FROM many_passengers"
]
},
{
"cell_type": "markdown",
"id": "6c315112-77c5-4a4c-ab19-55f80f43c88d",
"metadata": {},
"source": [
"This is what JupySQL executes:"
]
},
{
"cell_type": "code",
"execution_count": 24,
"id": "83aa9bcb-dd70-47a0-ae33-566b108dea1a",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"WITH many_passengers AS (\n",
"SELECT *\n",
"FROM taxi\n",
"WHERE passenger_count > 3\n",
"-- remove top 1% outliers for better visualization\n",
"AND trip_distance < 18.93)\n",
"SELECT MIN(trip_distance), AVG(trip_distance), MAX(trip_distance)\n",
"FROM many_passengers\n"
]
}
],
"source": [
"query = %sqlcmd snippets trip_stats\n",
"print(query)"
]
},
{
"cell_type": "markdown",
"id": "2e6388a7-e524-4c94-8b4d-6bac99622790",
"metadata": {},
"source": [
"## Plotting\n",
"\n",
"### Boxplot"
]
},
{
"cell_type": "code",
"execution_count": 31,
"id": "8785696d-5d86-4d22-b8a4-ac2a5d089e4c",
"metadata": {
"tags": []
},
"outputs": [
{
"data": {
"text/html": [
"Plotting using saved snippet : many_passengers"
],
"text/plain": [
"Plotting using saved snippet : many_passengers"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/plain": [
""
]
},
"execution_count": 31,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "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",
"text/plain": [
"