Data cardinality: what it is and how it shapes your dashboards
Max Musing
Max MusingFounder and CEO of Basedash
· August 26, 2026

Max Musing
Max MusingFounder and CEO of Basedash
· August 26, 2026

Data cardinality is the number of distinct values in a column. A country column has low cardinality (a couple hundred possible values), an order_status column has very low cardinality (a handful of states), and a user_id or email column has high cardinality (one distinct value per row in the extreme case). Cardinality is not a quality score. It is a property of your data that quietly decides whether a chart is readable, whether a filter is usable, and how much work a query has to do. When a dashboard is slow, a legend has 4,000 entries, or a dropdown never finishes loading, high cardinality is often the reason.
This guide is for analysts, operators, and engineers who build dashboards on real business data. It covers what cardinality means, why it matters for query performance and chart design, how databases use it to plan queries, and a practical playbook for taming high-cardinality columns without throwing away detail.
Cardinality counts distinct values, not rows. A table with 10 million orders might have:
order_id: ~10 million distinct values (one per row). Very high cardinality.customer_id: maybe 200,000 distinct values. High cardinality.product_id: maybe 5,000 distinct values. Medium cardinality.country: ~195 distinct values. Low cardinality.status: 5 distinct values (pending, paid, shipped, refunded, cancelled). Very low cardinality.The same column can shift cardinality depending on grain. A signup_date truncated to the month has low cardinality (12 values per year). The same timestamp kept to the second can have millions of distinct values. That distinction matters, because you often get to choose the grain, and choosing it well is one of the cheapest performance wins available.
Two terms are worth separating. In everyday BI usage, “cardinality” describes the distinct-value count of a single column. In data modeling, “cardinality” also describes the relationship between tables (one-to-one, one-to-many, many-to-many). This post is about the first meaning: how many distinct values live in a column, and what that does to your dashboards.
Cardinality shows up in four places, and each one bites differently.
Chart legibility. A bar chart with 12 categories is readable. The same chart grouped by customer_id produces thousands of bars, an unreadable legend, and a color palette that recycles until every series looks the same. A line chart broken out by a high-cardinality dimension becomes a solid block of overlapping lines. The chart is not wrong, it is just useless. High-cardinality dimensions belong in tables, search, or top-N views, not in a stacked bar or a pie.
Query cost. Grouping and counting distinct values over a high-cardinality column is expensive. GROUP BY customer_id over 10 million rows has to build and track a large number of groups. COUNT(DISTINCT user_id) has to keep track of every unique value it has seen, which grows with cardinality. On a transactional database serving your app, a heavy group-by can compete with production traffic. See our performance playbook for slow BI dashboards for the query-level fixes.
Filters and controls. A dropdown filter works when it holds a few dozen options. Point it at a 200,000-value customer_id column and it becomes unusable: slow to load, impossible to scroll, useless for finding one value. High-cardinality filters need a search-as-you-type control or a typed input, not a dropdown.
Storage and compression. Columnar analytics engines compress low-cardinality columns extremely well and high-cardinality columns poorly. Microsoft’s Power BI guidance is explicit that the VertiPaq engine hash-encodes non-numeric columns by assigning an identifier to each distinct value, so reducing cardinality is one of the most effective ways to shrink a model and speed up scans. The same logic applies to warehouses like Snowflake, BigQuery, ClickHouse, and Redshift: fewer distinct values means better compression and cheaper scans.
| Tier | Distinct values | Examples | Good for | Watch out for |
|---|---|---|---|---|
| Very low | 2 to ~20 | status, plan, is_active, country_group |
Grouping, pie/bar, filters, drill paths | Almost nothing |
| Low | ~20 to ~200 | country, channel, month, category |
Grouping, small-multiples, dropdown filters | Bar charts start to crowd near the top |
| Medium | ~200 to ~10k | product_id, city, sku, campaign |
Top-N views, searchable filters, tables | Full group-by charts get unreadable |
| High | ~10k to millions | customer_id, session_id, email |
Search, detail lookups, joins to a key | Never chart directly; distinct counts get expensive |
| Very high | ~1 per row | order_id, event_id, UUIDs |
Row identity, joins | Useless as a grouping dimension |
The tier is a design signal. When a field lands in the “high” or “very high” row, that is your cue to reach for a different chart type, a search filter, or an aggregation, rather than forcing it into a standard breakdown.
Cardinality is not just a dashboard concern. Your database uses estimated distinct-value counts to decide how to run a query in the first place. PostgreSQL stores an n_distinct estimate for each column in its statistics and uses it, along with most-common-value lists, to guess how many rows a WHERE clause or join will return. Those estimates drive the planner’s choice of scan and join strategy. When the estimate is wrong, often because statistics are stale after a big data change, the planner can pick a bad plan and a query that should take milliseconds takes minutes.
The practical takeaway: if a dashboard query is suddenly slow after a large load or backfill, refreshing table statistics (for example, ANALYZE in PostgreSQL) is often the first thing to try, before you start rewriting SQL.
You rarely want to delete a high-cardinality column, because it usually carries the identity you need for joins and lookups. The goal is to stop asking a dashboard to display or aggregate raw high-cardinality values directly. Here is the order to work through.
1. Ask whether it should be a dimension at all. A customer_id is a great join key and a terrible chart axis. If you find yourself grouping by an ID to make a chart, you probably want to group by an attribute of that entity instead: customer plan, region, signup_cohort, or industry. Push the high-cardinality key into the join and chart the low-cardinality attribute.
2. Bucket continuous values. Turn a raw amount, age, or duration into ranges: 0 to 10, 10 to 100, 100 to 1000. Truncate timestamps to the day, week, or month. Bucketing collapses thousands of distinct values into a handful and makes both charts and filters usable. This is the single most common fix.
3. Use top-N plus an “other” bucket. Show the top 10 or 20 values by revenue, count, or whatever matters, and roll the long tail into a single “other” row. A top-20 products chart answers the real question (“what sells?”) far better than a 5,000-bar chart nobody can read.
4. Replace dropdowns with search. For filters on medium and high-cardinality columns, use a search-as-you-type control or a free-text input backed by an indexed lookup. Never render a select box with 50,000 options. Modern BI tools, including Basedash, handle this by letting you type to filter instead of scrolling a giant list.
5. Use approximate distinct counts at scale. When you only need “roughly how many unique users,” an exact COUNT(DISTINCT) over a very high-cardinality column can be slow and memory-hungry. Warehouses offer approximate functions built on HyperLogLog: BigQuery’s APPROX_COUNT_DISTINCT trades a small error (roughly a fraction of a percent) for a large speedup, and Snowflake, Redshift, and ClickHouse have equivalents. Use exact counts for billing and finance, approximate counts for exploration and trend dashboards.
6. Pre-aggregate the heavy stuff. If a high-cardinality group-by runs on every dashboard load, compute it once. A nightly rollup table, a materialized view, or a summary model turns an expensive scan into a cheap lookup. This is also where a warehouse copy or a read replica earns its keep, so heavy reporting queries do not compete with your application. Our guide to modeling data for BI covers where to draw the grain.
High cardinality is fine, and even necessary, in several cases. Do not “optimize” it away:
order_id and customer_id are supposed to be unique or near-unique. That is their job.COUNT(DISTINCT) over a partitioned, well-clustered table may be perfectly fast. Measure before you assume.The discipline is simple: match the cardinality of a column to how you are using it. Use low-cardinality columns for grouping, coloring, and dropdowns. Use high-cardinality columns for identity, joins, search, and detail. Trouble starts when the two get swapped.
What is the difference between high and low cardinality?
Low cardinality means a column has few distinct values relative to its rows, like a status column with five states. High cardinality means many distinct values, like email or user_id where nearly every row is unique. It is a description of the data, not a judgment, but it strongly influences how you should chart, filter, and query the column.
Does high cardinality slow down queries? It can. Grouping by or counting distinct values over a high-cardinality column forces the database to track many groups or many unique values, which uses more memory and time. Databases also estimate cardinality to plan queries, so stale statistics on a high-cardinality column can lead to a bad plan. Bucketing, top-N, pre-aggregation, and approximate distinct counts are the usual fixes.
How do I reduce the cardinality of a column? Lower the grain. Truncate timestamps to a day or month, round or bucket numeric values into ranges, group rare categories into “other,” or replace a composite identifier with the specific attributes you actually filter on. The goal is fewer distinct values while keeping the detail you need for analysis.
Should I ever chart a high-cardinality column directly? Almost never as a full breakdown. A chart grouped by thousands of distinct values is unreadable and slow. Instead, show the top N values with an “other” bucket, chart a lower-cardinality attribute of the same entity, or move the raw values into a searchable table where they are viewed one row at a time.
Is cardinality the same as a many-to-many relationship? Not exactly. In data modeling, “cardinality” also describes how tables relate (one-to-one, one-to-many, many-to-many). In everyday BI and this guide, cardinality means the distinct-value count of a single column. Both matter, but they answer different questions: one is about relationships between tables, the other is about the variety of values inside one column.
Do I need a data warehouse to handle high-cardinality data? Not necessarily. Small and medium datasets on a transactional database handle high cardinality fine, especially with sensible filters and top-N views. A warehouse or read replica helps when heavy distinct counts or group-bys run often enough to compete with production traffic or slow the dashboard, at which point pre-aggregation and columnar storage make a real difference.
Written by

Founder and CEO of Basedash
Max Musing is the founder and CEO of Basedash, an AI-native business intelligence platform designed to help teams explore analytics and build dashboards without writing SQL. His work focuses on applying large language models to structured data systems, improving query reliability, and building governed analytics workflows for production environments.
Basedash lets you build charts, dashboards, and reports in seconds using all your data.