The reason why I like Unicode a lot is because it allows me to code in text based environments and still have nice output. Today, we’re going to play with Regional Indicator Symbol, which is implemented as a Unicode combinaison of letters from ๐Ÿ‡ฆ to ๐Ÿ‡ฟ. For instance, if you display ๐Ÿ‡ซ then ๐Ÿ‡ท concatenated together, you get ๐Ÿ‡ซ๐Ÿ‡ท. Let’s try that from our PostgreSQL prompt!

Context and Necessary data

In the historical record of motor racing data database we have all the data to reproduce the table from the List of Formula One seasons with driver’s champions and constructors champion.

Grab the MySQL version of it, the only one available now, and import its schema and data in a single command line with pgloader:

$ createdb f1db
$ pgloader mysql://root@localhost/f1db pgsql:///f1db
$ psql -d f1db -c 'ALTER DATABASE f1db SET search_path TO f1db, public;'

In the List of Formula One seasons page is included a flag from the country of each driver. The f1db database model only includes a nationality column for each driver, that we can see here:

  select count(*) as victories,
         forename, surname, nationality
    from drivers
         left join results
                on drivers.driverid = results.driverid
               and results.position = 1
group by drivers.driverid
order by victories desc
   limit 10;

Which gives the following result:

 victories โ”‚ forename  โ”‚  surname   โ”‚ nationality 
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        91 โ”‚ Michael   โ”‚ Schumacher โ”‚ German
        56 โ”‚ Lewis     โ”‚ Hamilton   โ”‚ British
        51 โ”‚ Alain     โ”‚ Prost      โ”‚ French
        45 โ”‚ Sebastian โ”‚ Vettel     โ”‚ German
        41 โ”‚ Ayrton    โ”‚ Senna      โ”‚ Brazilian
        32 โ”‚ Fernando  โ”‚ Alonso     โ”‚ Spanish
        31 โ”‚ Nigel     โ”‚ Mansell    โ”‚ British
        27 โ”‚ Jackie    โ”‚ Stewart    โ”‚ British
        25 โ”‚ Jim       โ”‚ Clark      โ”‚ British
        25 โ”‚ Niki      โ”‚ Lauda      โ”‚ Austrian
(10 rows)

Can we display directly the champion’s country flag in the result set?

The unicode trick

If we apply what we read before in the Regional Indicator Symbol page, displaying a Unicode country flag is as easy as concatenating the country codes together in the right Unicode range. Of course PostgreSQL knows how to do that, thanks to its advanced set of String Functions and Operators:

select chr(127462+ascii('F')-ascii('A')) as a,
       chr(127462+ascii('R')-ascii('A')) as b,
          chr(127462+ascii('F')-ascii('A'))
       || chr(127462+ascii('R')-ascii('A')) as flag;

And yes that’s a good reading of the Unicode specifications for Regional Indicator Symbol, it seems:

 a | b | flag 
---+---+------
 ๐Ÿ‡ซ | ๐Ÿ‡ท | ๐Ÿ‡ซ๐Ÿ‡ท
(1 row)

As it is quite cumbersome to write this expression each time, we can code a PostgreSQL function that would do that for us:

create or replace function flag
 (
   code text
 )
 returns text
language sql
as $$
 select    chr(  127462
               + ascii(substring(code from 1 for 1))
               - ascii('A'))

        || chr(  127462
               + ascii(substring(code from 2 for 1))
               - ascii('A'))
$$;

With that it’s easier:

select name, code, flag(code)
  from country
 where code is not null
 limit 10;

And here’s a nice list of flags now:

       name        | code | flag 
-------------------+------+------
 Afghanistan       | AF   | ๐Ÿ‡ฆ๐Ÿ‡ซ
 Albania           | AL   | ๐Ÿ‡ฆ๐Ÿ‡ฑ
 Algeria           | DZ   | ๐Ÿ‡ฉ๐Ÿ‡ฟ
 American Samoa    | AS   | ๐Ÿ‡ฆ๐Ÿ‡ธ
 Andorra           | AD   | ๐Ÿ‡ฆ๐Ÿ‡ฉ
 Angola            | AO   | ๐Ÿ‡ฆ๐Ÿ‡ด
 Anguilla          | AI   | ๐Ÿ‡ฆ๐Ÿ‡ฎ
 Antarctica        | AQ   | ๐Ÿ‡ฆ๐Ÿ‡ถ
 Antigua & Barbuda | AG   | ๐Ÿ‡ฆ๐Ÿ‡ฌ
 Argentina         | AR   | ๐Ÿ‡ฆ๐Ÿ‡ท
(10 rows)

Country Codes

This country table comes from http://data.okfn.org/data/core/country-codes. They have a GitHub repository at https://github.com/datasets/country-codes from which you can easily grab the CSV data file then integrate it into PostgreSQL:

begin;

create table public.country
 (
   name                             text,
   official_name_en                 text,
   official_name_fr                 text,
   code                             text,
   trigram                          text,
   M49                              text,
   ITU                              text,
   MARC                             text,
   WMO                              text,
   DS                               text,
   Dial                             text,
   FIFA                             text,
   FIPS                             text,
   GAUL                             text,
   IOC                              text,
   currency_alphabetic_code         text,
   currency_country_name            text,
   currency_minor_unit              text,
   currency_name                    text,
   currency_numeric_code            text,
   is_independent                   text,
   Capital                          text,
   Continent                        text,
   TLD                              text,
   Languages                        text,
   Geoname                          text,
   EDGAR                            text
 );

\copy public.country from 'country-codes.csv' with delimiter ',' csv header quote '"'

commit;

Nationality and Country Code Mapping

We said before that our Formula One database only has a nationality column for our drivers. How can we map that into country codes, so that we can diplay our nice little Unicode flag?

Well this time I failed to find a ready to use dataset, but I found an easy to scrap web page with the needed information at http://www.ef.com/english-resources/english-grammar/nationalities/.

Some lines of python later:

#! /usr/bin/env python3

import re

def parse_nationalities(filename):
    regexp = '\t\t\t\t(.*)</td>$'

    # Given that regexp we want to keep the first match as the country name
    # then the second match as the nationality, then skip the third line
    current = None
    skip = False

    with open(filename, 'r') as f:
        for line in f:
            match = re.search(regexp, line)
            if match:
                if skip:
                    skip = False
                elif current is None:
                    current = match.group(1)
                else:
                    print("%s;%s" % (current, match.group(1)))
                    current = None
                    skip = True

if __name__ == "__main__":
    parse_nationalities('nationalities.html')

It is now possible to load the data set thanks to the following SQL:

begin;

create table public.nationalities
 (
   country     text,
   nationality text
 );


\copy public.nationalities from 'nationalities.csv' with delimiter ';' csv

insert into public.nationalities(nationality, country)
     values ('American', 'US'),
            ('American-Italian', 'US'),
            ('Argentine-Italian', 'Argentina'),
            ('Chilean', 'Chile'),
            ('East German', 'Germany'),
            ('Liechtensteiner', 'Liechtenstein'),
            ('Monegasque', 'Monaco'),
            ('New Zealander', 'New Zealand'),
            ('Rhodesian', 'Zimbabwe'),
            ('Venezuelan', 'Venezuela');

update public.nationalities
   set country = 'Netherlands'
 where country = 'Holland';

update public.nationalities
   set country = 'US'
 where country = 'The United States';

update public.nationalities
   set country = 'UK'
 where country = 'Britain';

update public.nationalities
   set country = 'Czech Republic'
 where country = 'the Czech Republic';

commit;

Note that to have matches for all our drivers, we did need to edit the list provided so that it would match with the set of nationalities found in the f1db database. You can list those with the following query:

select distinct(nationality) from drivers;

And more to the point, you can find any missing nationality in your new table set with the following anti-join query:

select nationality, country.code
  from drivers
       left join nationalities using(nationality)
       left join country
              on country.name = nationalities.country
 where country.code is null;

Thanks to the modification we did previously after importing the dataset, this query should return an empty result set.

Formula One drivers and country flags

We now have enough data to finally play our unicode trick and display driver’s country flags directly in our query result set on the console:

  select count(*) as victories,
         forename, surname,
         flag(country.code)
    from drivers
         left join results
                on drivers.driverid = results.driverid
               and results.position = 1
         join nationalities using(nationality)
         join country
           on country.name = nationalities.country
group by drivers.driverid, country.code
order by victories desc
   limit 10;

And here’s our result with inline colored flags in the terminal:

 victories | forename  |  surname   | flag 
-----------+-----------+------------+------
        91 | Michael   | Schumacher | ๐Ÿ‡ฉ๐Ÿ‡ช
        56 | Lewis     | Hamilton   | ๐Ÿ‡ฌ๐Ÿ‡ง
        51 | Alain     | Prost      | ๐Ÿ‡ซ๐Ÿ‡ท
        45 | Sebastian | Vettel     | ๐Ÿ‡ฉ๐Ÿ‡ช
        41 | Ayrton    | Senna      | ๐Ÿ‡ง๐Ÿ‡ท
        32 | Fernando  | Alonso     | ๐Ÿ‡ช๐Ÿ‡ธ
        31 | Nigel     | Mansell    | ๐Ÿ‡ฌ๐Ÿ‡ง
        27 | Jackie    | Stewart    | ๐Ÿ‡ฌ๐Ÿ‡ง
        25 | Jim       | Clark      | ๐Ÿ‡ฌ๐Ÿ‡ง
        25 | Niki      | Lauda      | ๐Ÿ‡ฆ๐Ÿ‡น
(10 rows)

Fun, right?