How to Build a Customer Health Score Directly From Your Database
Every SaaS company has the same problem: customer success managers need to know which accounts are at risk before it's too late. But the data that actually reveals customer health login frequency, feature usage, support ticket volume, billing status lives in your database, not in a tidy dashboard.
Most teams respond to this by stitching together spreadsheets, writing one-off SQL reports, or waiting for a data engineer to build something. By the time the report lands, the customer has already decided to leave.
This article walks through how to build a meaningful customer health score using data that's already in your database and how to automate it so your team always has a current picture, without anyone writing a query.
What Goes Into a Customer Health Score
A health score is a composite number that summarises how well a customer is doing. Done right, it predicts churn risk weeks before the customer actually churns.
The signals that matter most vary by product, but most B2B SaaS companies track some version of these:
Usage signals
Account signals
Support signals
Engagement signals
Each signal gets a weight. The final score is typically a number from 0–100, where below 40 means "at risk," 40–70 is "neutral," and above 70 is "healthy."
The SQL Behind It
Let's make this concrete. Assume you have a PostgreSQL database with tables like users, sessions, subscriptions, support_tickets, and accounts.
Here's a query that calculates a simplified health score per account:
WITH usage AS (
SELECT
account_id,
COUNT(DISTINCT DATE(created_at)) AS active_days_last_30,
MAX(created_at) AS last_session
FROM sessions
WHERE created_at >= NOW() - INTERVAL '30 days'
GROUP BY account_id
),
support AS (
SELECT
account_id,
COUNT(*) FILTER (WHERE status = 'open') AS open_tickets
FROM support_tickets
GROUP BY account_id
),
billing AS (
SELECT
account_id,
status AS billing_status,
CASE WHEN status = 'active' THEN 20 ELSE 0 END AS billing_score
FROM subscriptions
WHERE is_current = true
)
SELECT
a.id AS account_id,
a.name,
COALESCE(u.active_days_last_30, 0) AS active_days,
COALESCE(s.open_tickets, 0) AS open_tickets,
b.billing_status,
(
-- Usage score: up to 50 points
LEAST(COALESCE(u.active_days_last_30, 0) * 2, 50) +
-- Billing score: 20 points for active
COALESCE(b.billing_score, 0) +
-- Support penalty: -10 per open ticket, floor at 0
GREATEST(-10 * COALESCE(s.open_tickets, 0), -30)
) AS health_score
FROM accounts a
LEFT JOIN usage u ON u.account_id = a.id
LEFT JOIN support s ON s.account_id = a.id
LEFT JOIN billing b ON b.account_id = a.id
ORDER BY health_score ASC;This gives you a ranked list of accounts, worst-first. Your customer success team can start making calls from the top.
The weights are illustrative you'll tune them based on what actually predicts churn in your data. But the structure is reusable.
Adding Feature Adoption Depth
Usage frequency alone doesn't tell the whole story. A customer might log in every day but only use one feature, which is a warning sign for expansion and retention.
Feature adoption depth requires tracking what features each account has actually used. If you store feature events in a table:
SELECT
account_id,
COUNT(DISTINCT feature_name) AS features_used,
-- assume 10 core features total
ROUND(COUNT(DISTINCT feature_name)::numeric / 10 * 100, 0) AS adoption_pct
FROM feature_events
WHERE created_at >= NOW() - INTERVAL '60 days'
GROUP BY account_id;Layer this into your health score as another weighted component. Accounts using fewer than 30% of core features get a lower score; accounts using 70%+ get a bonus.
The Problem With Manual Queries
If you run this query once, you have a snapshot. But customer health is a moving target. An account that was healthy last week might have two support tickets open today after a failed deployment.
The two failure modes:
Most teams handle this with scheduled Tableau/Metabase reports, which means a data analyst has to own the query, maintain it, and make sure it runs. When they leave or get busy, the report breaks.
Automating Health Scores With AI for Database
This is where AI for Database changes the workflow entirely. Instead of maintaining a scheduled query someone has to own, you connect your database and set up two things:
A live dashboard a plain-English query like "Show me all accounts with fewer than 5 active days in the last 30 days, sorted by open support tickets" returns a live table. No SQL required. Non-technical CS managers can check it themselves.
An action workflow define the condition once: "When an account's health score drops below 40 (based on active_days < 5 AND open_tickets > 2)..." and connect an action: send a Slack alert, trigger a webhook to your CRM, or fire an email to the account owner.
Condition: active_days_last_30 < 5 AND open_tickets > 1
Action: POST to Slack webhook → "#cs-alerts: Account {name} may need attention"Your customer success team gets alerted in Slack the moment an account crosses into risk territory before anyone has to remember to run a query.
Segmenting Your Health Score by Plan or Segment
Enterprise accounts and small business accounts need different thresholds. An SMB using the product 5 days a month might be perfectly healthy; an enterprise account doing the same is underperforming relative to the contract value.
Segment your scoring:
SELECT
a.id,
a.name,
a.plan_tier,
CASE
WHEN a.plan_tier = 'enterprise' THEN
-- stricter thresholds
LEAST(COALESCE(u.active_days_last_30, 0) * 3, 60) + ...
ELSE
LEAST(COALESCE(u.active_days_last_30, 0) * 2, 50) + ...
END AS health_score
FROM accounts a
LEFT JOIN usage u ON u.account_id = a.id;Once you've built this segmentation, you can expose it to your CS team as a simple question: "Which enterprise accounts have a health score below 50?" They get the answer without touching SQL.
Tracking Health Score Trends Over Time
A single health score is useful. A trend is more useful. An account at 45 today that was at 70 last month is declining fast. An account at 45 that was at 30 last month is recovering.
To track trends, you need to snapshot scores periodically. The simplest approach: write a scheduled job that inserts daily health scores into a account_health_snapshots table.
INSERT INTO account_health_snapshots (account_id, score, snapshot_date)
SELECT id, calculated_score, CURRENT_DATE
FROM v_account_health_scores; -- the view from your composite queryNow you can ask "Show me accounts whose health score has dropped by more than 15 points in the last 14 days" exactly the kind of question AI for Database can answer in plain English against your live data.
Start Simple, Iterate Fast
The teams that build the best health scores don't start with a perfect model. They start with two signals, see which accounts it identifies, talk to customers in that cohort, and refine the weights based on what they learn.
If you have a database and want to start querying your customer health data today without writing SQL, try AI for Database free. Connect your database, ask "Which customers haven't logged in for 30 days?" and you'll have your first at-risk list in minutes.
Start querying your database for free → Connect in 2 minutes at aifordatabase.com, no SQL required.
Frequently Asked Questions
What's the best starting point for a customer health score if I've never built one before?
Start with the two signals you can measure most reliably: login frequency and billing status. Even a two-variable score is better than no score. Add more dimensions over time as you validate which signals actually predict churn in your specific product.
How often should the health score refresh?
Daily is usually sufficient for most SaaS products. If your product has high-frequency usage (multiple sessions per day), consider hourly refreshes for the usage component. Billing status and support tickets can refresh daily.
Should I expose the health score to customers?
Some companies do (Gainsight has pioneered this). The benefit is that customers can self-diagnose and take action. The risk is that customers game the score rather than actually using the product. Generally keep scores internal unless you've thought through the incentives carefully.
How do I handle accounts with no activity in the last 30 days?
Give them a score near zero but don't delete them from the view they need the most attention. Flag them separately as "inactive" in addition to "at risk" so your team knows the account needs re-engagement, not just check-in.
Can I build this without a data engineer?
Yes. If you have database access and a tool like AI for Database, you can query the data using plain English, build the dashboard yourself, and set up alerting workflows without writing or maintaining SQL. The hard part is deciding on the scoring formula that's a business decision, not a technical one.
What databases work for building health scores?
Any relational database works well PostgreSQL, MySQL, SQL Server. MongoDB and document databases require more effort to aggregate signals efficiently. If you're on Supabase or PlanetScale, you have full PostgreSQL-compatible querying available.
How is this different from tools like Gainsight or Totango?
Gainsight and Totango are purpose-built customer success platforms with health scoring built in. They're powerful but expensive (often $50K+/year) and require you to sync your data to their platform. Building from your own database gives you full control over the scoring model, zero vendor lock-in, and real-time data without an ETL pipeline.