Skip to main content

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.

DatabaseEngine example
PostgreSQLpostgresql+psycopg://user:pass@host/db
MySQLmysql+pymysql://user:pass@host/db
MariaDBmysql+pymysql://user:pass@host/db
SQLitesqlite:///app.db
Oracleoracle+oracledb://user:pass@host/db
SQL Servermssql+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

TipDetail
Use bound paramsAlways pass query values through params= to avoid SQL injection.
Install drivers oncepip install psycopg for Postgres, PyMySQL for MySQL, and so on. Vizly handles the rest.
Aggregate in SQLPush GROUP BY and time bucketing into the query. Charts stay small and fast.
Engine reuseCreate one engine per database and reuse it. SQLAlchemy pools connections for you.
Files work tooPrefer from_csv / from_excel when data is not in a database. See the file ingest recipe.