SQL Database Dashboards with Vizly
Your data lives in a database, not in a CSV someone emailed you. Vizly loads query results directly through SQLAlchemy, so you can chart Postgres, MySQL, MariaDB, SQLite, Oracle, SQL Server, or any database SQLAlchemy can reach. No exporting tables to files first, no pandas plumbing in between.
The from_sql loader
vz.from_sql(statement, bind=engine) runs the query and returns a tabular view you can pass straight to any chart factory. Pass an existing SQLAlchemy Engine, or let Vizly build one from a connection URL.
import vizly as vz
from sqlalchemy import create_engine
engine = create_engine("postgresql+psycopg://user:[email protected]:5432/sales")
table = vz.from_sql(
"SELECT day, SUM(revenue) AS revenue FROM orders GROUP BY day ORDER BY day",
bind=engine,
)
chart = vz.line(table, x="day", y="revenue", title="Daily revenue")
chart.to_html()
Database coverage
Vizly does not bundle every database driver. It depends on SQLAlchemy, and whatever SQLAlchemy can connect to, Vizly can chart. Install the DBAPI driver for your database and pass in an engine or URL.
| Database | Engine example |
|---|---|
| PostgreSQL | postgresql+psycopg://user:pass@host/db |
| MySQL | mysql+pymysql://user:pass@host/db |
| MariaDB | mysql+pymysql://user:pass@host/db |
| SQLite | sqlite:///app.db |
| Oracle | oracle+oracledb://user:pass@host/db |
| SQL Server | mssql+pyodbc://user:pass@host/db |
Anything with an external SQLAlchemy dialect works too: Snowflake, BigQuery, Redshift, ClickHouse, Databricks, MongoDB, and others.
Passing a URL directly
If you do not want to create an engine yourself, from_sql accepts a url=:
table = vz.from_sql(
"SELECT region, SUM(sales) AS sales FROM orders GROUP BY region",
url="postgresql+psycopg://user:[email protected]:5432/sales",
)
vz.bar(table, x="region", y="sales", title="Sales by region")
Parameterized queries
Use params= for safe, bound query parameters. Never f-string user input into SQL.
def region_dashboard(region: str):
table = vz.from_sql(
"""
SELECT product, SUM(sales) AS sales
FROM orders
WHERE region = :region
GROUP BY product
""",
bind=engine,
params={"region": region},
)
return vz.bar(table, x="product", y="sales", title=f"{region} sales")
Multi-database dashboard
You can mix sources in one dashboard. Pull financials from Postgres, operational metrics from MySQL, and reference data from SQLite, then compose them into a single page.
import vizly as vz
from sqlalchemy import create_engine
pg = create_engine("postgresql+psycopg://user:[email protected]:5432/analytics")
my = create_engine("mysql+pymysql://user:[email protected]:3306/ops")
lite = create_engine("sqlite:///reference.db")
revenue = vz.from_sql(
"SELECT day, SUM(revenue) AS revenue, SUM(cost) AS cost FROM financials GROUP BY day",
bind=pg,
)
latency = vz.from_sql(
"SELECT time, AVG(latency_ms) AS value FROM requests GROUP BY time",
bind=my,
)
targets = vz.from_sql("SELECT region, target FROM targets", bind=lite)
vz.set_theme("corporate")
chart = vz.page(
charts=[
vz.combo(revenue, x="day", bar="cost", line="revenue", title="Revenue vs cost"),
vz.line(latency, x="time", y="value", title="API latency (ms)"),
vz.bar(targets, x="region", y="target", title="Regional targets"),
],
title="Company dashboard",
)
chart.render("company.html")
Full dashboard with a live SQL refresh
Combine from_sql with set_data to refresh a chart when the database changes, without rebuilding the whole embed.
def load_metrics():
return vz.from_sql(
"SELECT time, value FROM metrics WHERE ts > now() - interval '1 hour'",
bind=engine,
)
chart = vz.line(load_metrics(), x="time", y="value", title="Live metrics")
# Later, when new rows arrive:
chart.set_data(load_metrics())
html_fragment = chart.live_update_script(chart.id)
Tips
| Tip | Detail |
|---|---|
| Use bound params | Always pass query values through params= to avoid SQL injection. |
| Install drivers once | pip install psycopg for Postgres, PyMySQL for MySQL, and so on. Vizly handles the rest. |
| Aggregate in SQL | Push GROUP BY and time bucketing into the query. Charts stay small and fast. |
| Engine reuse | Create one engine per database and reuse it. SQLAlchemy pools connections for you. |
| Files work too | Prefer from_csv / from_excel when data is not in a database. See the file ingest recipe. |