Help build the future of open source observability software Open positions

Check out the open source projects we support Downloads

The actually useful free plan

Grafana Cloud Free Tier
check

10k series Prometheus metrics

check

50GB logs, 50GB traces, 50GB profiles

check

500VUk k6 testing

check

20+ Enterprise data source plugins

check

100+ pre-built solutions

Featured webinar

Getting started with grafana LGTM stack

Getting started with managing your metrics, logs, and traces using Grafana

Learn how to unify, correlate, and visualize data with dashboards using Grafana.

How to use SQL to learn more about your Grafana usage

How to use SQL to learn more about your Grafana usage

2025-08-08 6 min

Wilfried Roset is an engineering manager who leads an SRE team, and he is also a Grafana Champion. Wilfried currently works at OVHcloud, where he focuses on prioritizing sustainability, resilience, and industrialization to guarantee customers satisfaction.

Grafana needs a database to store all its objects, such as users, dashboards, or even data sources. Each time a user creates a dashboard, it results in a new row created in the database. This database backs everything in Grafana, which makes it a gold mine of information.

In this blog I’ll show you how to take advantage of all those insights. But first, let’s take a quick look at how this all works.

Grafana exposes its objects via an HTTP API, which you can essentially think of as the public API. Upon API call, Grafana manipulates the objects in the database and applies its business logic to them. And you can think of SQL queries as the internal API of Grafana, since there’s a direct connection between what is exposed via the HTTP API and the objects in the database. 

Still not convinced? Here is an example. 

Let’s say you create a dashboard in Grafana via a POST /api/dashboards/db, as per the documentation. But what the documentation does not say is that the dashboard is stored in the table dashboard.This is also true when you create a data source via a POST /api/datasources — it will be stored in the table data_source.

This table is exactly what we will use. With access to this database and a basic SQL expertise you can learn a lot about how a Grafana instance is used. Let’s have a quick look at a few examples.

Disclaimer:

  • The blog post is written with the database of a Grafana instance running v10.1 backend by a PostgreSQL database. The SQL queries are subject to change.
  • All of the information gathered via SQL queries are also accessible via Grafana’s API.
  • Users must refrain from modifying the database objects. This could lead to breaking your database immediately or in the next upgrade!

Organizations, users, dashboards, and data sources

First, we’re looking for general information such as number of users, dashboards, or data_source. (There is a pivot table in Grafana that makes the multi-tenant experience great.)

Grafana offers a nice way to organize yourself via organization. For example, your users might have access to an organization or your dashboards are visible in a given organization. Let’s start by looking at the number organizations, users, dashboards, and data sources in your instance:

The number of organizations

grafana=> select count(*) from org;
 count
-------
    95
(1 row)

The number of users

grafana=> select count(*) from "user";
 count
-------
  2165
(1 row)

Beware that double-quotes are mandatory because user is a reserved keyword in PostgreSQL. Without them your query returns the internal information in PostgreSQL rather than the content of the Grafana’s table user.

The number of dashboards

grafana=> select count(*) from dashboard where is_folder= 'f';
 count
-------
  3684
(1 row)

The number of data_sources

sql
grafana=> select count(*) from data_source;
 count
-------
  1947
(1 row)

Digging deeper with SQL joins

Now that we have some general information, we can zoom in. This is doable with SQL joins in any way you like. We could, for example, try to identify the organization with the most users or, more specifically, the organization with the most users with Admin privileges.

The number of users per organization

grafana=> select org_id, count(*) from org_user group by org_id order by count desc limit 5;
 org_id | count
--------+-------
      1 |  2118
     94 |   424
     96 |   222
      2 |   202
    106 |   156
(5 rows)

The number of users with Admin privileges per organization

grafana=> select org_id, count(*) from org_user where role = 'Admin' group by org_id order by count desc limit 5;

 org_id | count

--------+-------

     96 |    30

     24 |    25

    537 |    23

    112 |    19

      4 |    16

(5 rows)

The list of all users with Admin privileges

grafana=> select distinct(u.login) from "user" u join org_user ou on u.id = ou.user_id where ou.role = 'Admin'  limit 10;
       login
-------------------
 Bulbasaur
 Ivysaur
 Venusaur
 Charmander
 Charmeleon
 Charizard
 Squirtle
 Wartortle
 Blastoise
 Caterpie
(10 rows)

Understanding user behavior

Now, let’s dig a little deeper to better understand our users. For example, we might want to know the most used data_source type per organization.

grafana=> select type, count(*) from data_source group by type order by count desc limit 5;
             type              | count
-------------------------------+-------
 prometheus                    |   833
 grafana-opensearch-datasource |   349
 elasticsearch                 |   211
 opentsdb                      |   134
 marcusolsson-json-datasource  |    98
(5 rows)

With this information, we know how to better support our users by providing the appropriate training. However, we don’t know which organization uses the most prometheus. This information is just a SQL query away.

grafana=> select org_id, count(*) from data_source where type = 'prometheus' group by org_id order by count desc limit 5;
 org_id | count
--------+-------
      1 |   206
    112 |    72
      4 |    57
    695 |    37
      2 |    31
(5 rows)

Great, we have the organization ID but this is not enough to know who to give training to. By joining the tables user, org_user, and org, we can have in a single statement:

  • The organization ID
  • The organization name
  • The list of the users with Admin privileges within a given organization
grafana=> select o.id as org_id, o.name as org_name, array_agg(u.login) as admin from "user" u join org_user ou on u.id = ou.user_id join org o on o.id = ou.org_id where ou.role = 'Admin' group by o.name, ou.role, o.id  order by 1, 2 limit 3;
 org_id |             org_name              |                                                                                                                       admin
--------+-----------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
      1 | Foo                               | {Bulbasaur,Charmander}
      2 | Bar                               | {Charmeleon,Squirtle,Blastoise}
     12 | Baz                               | {Venusaur}
(3 rows)

As you can see, you can learn a lot by exploring Grafana’s database. There is still a lot more that can be done to understand how your Grafana instance is used, but the queries can become harder to write, read, and understand. Let’s keep this for another time… 

Or not. Here is OVHcloud’s most used panel type across our 3,000 dashboards.

grafana=> SELECT
  elements.value AS panel_type,
  COUNT(*) AS count
FROM
  dashboard,
  jsonb_array_elements_text(jsonb_path_query_array(data::jsonb, '$.panels[*].type ? (@ != "row")')) AS elements
GROUP BY
  elements.value
ORDER BY
  count DESC;
              panel_type               | count
---------------------------------------+-------
 timeseries                            | 16045
 stat                                  |  7007
 table                                 |  2630
 graph                                 |  1691
 gauge                                 |  1443
 singlestat                            |   972
 text                                  |   937
 piechart                              |   906
 bargauge                              |   603
 barchart                              |   311
 grafana-piechart-panel                |   197
 state-timeline                        |   135
 table-old                             |   132
 heatmap                               |   123
 dashlist                              |    76
 petrslavotinek-carpetplot-panel       |    55
 logs                                  |    43
 status-history                        |    42
 grafana-clock-panel                   |    37
 geomap                                |    26
 alertlist                             |    25
 netsage-sankey-panel                  |    24
 marcusolsson-treemap-panel            |    21
 histogram                             |    20
 grafana-polystat-panel                |    14
 knightss27-weathermap-panel           |     8
 grafana-worldmap-panel                |     6
 natel-discrete-panel                  |     5
 trend                                 |     4
 xychart                               |     4
 welcome                               |     3
 traces                                |     2
 jdbranham-diagram-panel               |     2
 flant-statusmap-panel                 |     2
 yesoreyeram-boomtable-panel           |     2
 alexanderzobnin-zabbix-triggers-panel |     1
 nodeGraph                             |     1
 canvas                                |     1
(38 rows)

Time: 1413.119 ms (00:01.413)
A screenshot of a popular panels dashboard based on a SQL query

SQL is awesome, especially PostgreSQL with its powerful jsonb features. If you like challenges, you can try to produce the same information by using Grafana’s API—with the same response time.

Want to share your Grafana story and dashboards with the community? Drop us a note at stories@grafana.com__.