Grafana, SQL and IN() list for multi-value variable
Tags: grafana, sql, list
Published: 2021-03-08 19:46:03

---
title: Grafana, SQL and IN() list for multi-value variable
published: true
tags: Grafana, SQL, list
canonical_url: https://blog.dbi-services.com/grafana-sql-and-in-list-for-multi-value-variable
---
I've recently looked at Grafana. Pros: it is really nice, I mean aesthetically. If you want to build a dashboard for your boss, or put it on a large screen in the open space, that's beautiful. Cons: not mature. I'm working for years with technology that works. When I have a problem, I find the solution (blog posts, forums, mailing lists, support notes)... But with modern software, the first problems I encounter have a dead end in unresolved git issues. And I'm actually spending more time in finding workarounds than solving problems. Yes, Grafana is nice but is very far from what I can do with Excel in 5 minutes. However, when I find a workaround, I publish it to help others and hopefully find someone who has a better solution, so please leave a comment if what I do there is completely silly.

<h3>The problem to solve: IN() list in SQL from a multi-value variable</h3>

For this example I've created a <a href="https://console.cloud.google.com/sql/instances?hl=fr&amp;_ga=2.176241317.1215146593.1613680030-1358948016.1609170432" rel="noopener" target="_blank">PostgreSQL database in Google Cloud SQL</a>, loaded <a href="https://github.com/gvenzl/sample-data/blob/master/countries-cities-currencies/install.sql" rel="noopener" target="_blank">sample data from Gerald Venzl</a> and a <a href="https://grafana.com/signup/cloud/connect-account" rel="noopener" target="_blank">free grafana cloud service</a>. My goal is to get something like that where I can choose one, many, all or no country at all:
<a href="https://blog.dbi-services.com/wp-insides/uploads/sites/2/2021/02/Screenshot-2021-02-18-231601-scaled.jpg"><img src="https://blog.dbi-services.com/wp-insides/uploads/sites/2/2021/02/Screenshot-2021-02-18-231601-scaled.jpg" alt="" width="2560" height="1396" class="aligncenter size-full wp-image-47658" /></a>

I can query the list of countries as:
<pre><code>
select country_id __value , name __text from countries order by 2
</code></pre>
I alias my columns with "__value" and "__text" to show the country name to the user but get the country code from what she selected.

So I've created the Grafana variable with this query, and selected "Multi-value" and "Include All option" to let the user chose any combination:
<a href="https://blog.dbi-services.com/wp-insides/uploads/sites/2/2021/02/Screenshot-2021-02-18-220732-scaled.jpg"><img src="https://blog.dbi-services.com/wp-insides/uploads/sites/2/2021/02/Screenshot-2021-02-18-220732-scaled.jpg" alt="" width="2560" height="1493" class="aligncenter size-full wp-image-47655" /></a>

Now, let's try to use this variable, that I called "mylist" for the demo, in a SQL statement:
<pre><code>
select * from cities where country_id in ($mylist)
</code></pre>
I'll show the query generated for the different user combinations

<pre><code>
select * from cities where country_id in ('AND','ARE','AFG','ATG','ALB','ARM','AGO','ARG','AUT','AUS','AZE','BIH','BRB','BGD','BEL','BFA','BGR','BHR','BDI','BEN','BRN','BOL','BRA','BHS','BTN','BWA','BLR','BLZ','CAN','COD','CAF','COG','CHE','CIV','CHL','CMR','CHN','COL','CRI','CUB','CPV','CYP','CZE','DEU','DJI','DNK','DMA','DOM','DZA','ECU','EST','EGY','ERI','ESP','ETH','FIN','FJI','FSM','FRA','GAB','GBR','GRD','GEO','GHA','GMB','GIN','GNQ','GRC','GTM','GNB','GUY','HND','HRV','HTI','HUN','IDN','IRL','ISR','IND','IRQ','IRN','ISL','ITA','JAM','JOR','JPN','KEN','KGZ','KHM','KIR','COM','KNA','PRK','KOR','KWT','KAZ','LAO','LBN','LCA','LIE','LKA','LBR','LSO','LTU','LUX','LVA','LBY','MAR','MCO','MDA','MNE','MDG','MHL','MKD','MLI','MMR','MNG','MRT','MLT','MUS','MDV','MWI','MEX','MYS','MOZ','NAM','NER','NGA','NIC','NLD','NOR','NPL','NRU','NZL','OMN','PAN','PER','PNG','PHL','PAK','POL','PRT','PLW','PRY','QAT','ROU','SRB','RUS','RWA','SAU','SLB','SYC','SDN','SWE','SGP','SVN','SVK','SLE','SMR','SEN','SOM','SUR','SSD','STP','SLV','SYR','SWZ','TCD','TGO','THA','TJK','TLS','TKM','TUN','TON','TUR','TTO','TUV','TWN','TZA','UKR','UGA','USA','URY','UZB','VAT','VCT','VEN','VNM','VUT','WSM','XKX','YEM','ZAF','ZMB','ZWE')
</code></pre>
This is what I have when the user selects "All" and that's correct.

<pre><code>
select * from cities where country_id in ('BEL','NLD','LUX')
</code></pre>
This is what I get when the user selects some countries,and this again is correct.

<pre><code>
select * from cities where country_id in ()
</code></pre>
This is what I get when the user selects nothing. And I see no option in Grafana variables to disable this possibility. This is an invalid SQL syntax and gets, in PostgreSQL: pq: syntax error at or near ")", MySQL would show: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ')' at line 1, Oracle would show: ORA-00936: missing expression, SQL Server would show: Incorrect syntax near ')'. Grafana colorful charts are now less appealing when we get an non-user-friendly message like this. I need a workaround.

<h3>Not so advanced formats</h3>

I checked the <a href="https://grafana.com/docs/grafana/latest/variables/advanced-variable-format-options/" rel="noopener" target="_blank">"Advanced variable format options"</a> with the hope that "advanced" options should have a format for the most common data query language which is SQL. But not. You have CSV, OpenTSDB, Doublequote, Graphite, JSON, Elasticsearch, Percentencode, Pipe, Raw, Regex, Singlequote, Sqlstring, Text, URL formats, but nothing about SQL! Yes, SQL can filter on regexp but I want index access, partition pruning which are possible with equality or IN() lists only.

<h3>Nested variables and SQL transformation</h3>

Here is my workaround. I've created a second variable, that will be hidden, to build the IN() list from the list of value. And because I have a SQL engine, I'll use it to do the transformation:
<pre><code>
select case when length($$${mylist:singlequote}$$)&gt;0 then $$ in ($mylist:singlequote) $$ else ' is null ' end
</code></pre> 
Here I expand the previous variable with the "singlequote" format, which is the default for SQL queries. But I encapsulate it in a CASE statement. When the list is not empty, my variable builds the IN(...) list but when it is empty I choose to replace it with a IS NULL to let the user select unknown values. Of course you can decide the behaviour with a predicate that is always true or false.
<a href="https://blog.dbi-services.com/wp-insides/uploads/sites/2/2021/02/Screenshot-2021-02-18-233104.jpg"><img src="https://blog.dbi-services.com/wp-insides/uploads/sites/2/2021/02/Screenshot-2021-02-18-233104.jpg" alt="" width="2322" height="1410" class="aligncenter size-full wp-image-47661" /></a>
Then here is my dashboard panel query:
<pre><code>
select * from cities where country_id ${mylist_sql:raw}
</code></pre>
which expands to the same as when I used ${mylist} when the user chooses one, many, or all values.

But when the user selects nothing:
<pre><code>
select * from cities where country_id is null
</code></pre>
No error and the behavior I've chosen. Here no rows with no work because the optimizer knows that the primary key cannot be null.