How can I do This? – With SQL of Course!


Haven’t we all been wondering:

How can I do this? I have these data in Excel and I want to group / sort / assign / combine …

While you could probably pull up a Visual Basic script doing the work or export the data to Java or any other procedural language of choice, why not just use SQL?

The use-case: Counting neighboring colours in a stadium choreography

This might not be an everyday use-case for many of you, but for our office friends at FanPictor, it is. They’re creating software to draw a fan choreography directly into a stadium. Here’s the use-case on a high level:

Draw your fan choreography with FanPictor

Draw your fan choreography with FanPictor. In this case, a tribute to Roger Federer

It’s immediately clear what this fun software does, right?

  • You submit a choreography suggestion
  • The event organisers choose the best submission
  • The event organisers export the choreography as an Excel file
  • The Excel file is fed into a print shop, printing red/red, red/white, white/red, white/white panels (or any other colours)
  • The event helpers distribute the coloured panels on the appropriate seat
  • The fans get all excited

Having a look at the Excel spreadsheet

So this is what the Excel spreadsheet looks like:

Print shop instructions

Print shop instructions

Now, distributing these panels is silly, repetitive work. From experience, our friends at FanPictor wanted to have something along the lines of this, instead:

Print shop instructions for dummies

Print shop instructions for dummies

Notice that there are instructions associated with each panel to indicate:

  • … whether a consecutive row of identical panels starts or stops
  • … how many identical panels there are in such a row

“consecutive” means that within a stadium sector and row, there are adjacent seats with the same (Scene1, Scene2) tuple.

How do we solve this problem?

We solve this problem with SQL of course – and with a decent database, that supports window functions, e.g. PostgreSQL, or any commercial database of your choice! (you won’t be finding this sort of feature in MySQL).

Here’s the query:

with data
as (
  select 
    d.*,
    row(sektor, row, scene1, scene2) block
  from d
)
select 
  sektor,
  row,
  seat,
  scene1,
  scene2,
  case 
    when lag (block) over(o) is distinct from block 
     and lead(block) over(o) is distinct from block 
    then 'start / stop'
    when lag (block) over(o) is distinct from block 
    then 'start'
    when lead(block) over(o) is distinct from block 
    then 'stop'
    else ''
  end start_stop,
  count(*) over(
    partition by sektor, row, scene1, scene2
  ) cnt
from data
window o as (
  order by sektor, row, seat
)
order by sektor, row, seat;

That’s it! Not too hard, is it?

Let’s go through a couple of details. We’re using quite a few awesome SQL standard / PostgreSQL concepts, which deserve to be explained:

Row value constructor

The ROW() value constructor is a very powerful feature that can be used to combine several columns (or rows) into a single ROW / RECORD type:

row(sektor, row, scene1, scene2) block

This type can then be used for row value comparisons, saving you a lot of time comparing column by column.

The DISTINCT predicate

lag (block) over(o) is distinct from block 

The result of the above window function is compared with the previously constructed ROW by using the DISTINCT predicate, which is a great way of comparing things “null-safely” in SQL. Remember that SQL NULLs are some of the hardest things in SQL to get right.

Window functions

Window functions are a very awesome concept. Without any GROUP BY clause, you can calculate aggregate functions, window functions, ranking functions etc. in the context of a current row while you’re projecting the SELECT clause. For instance:

count(*) over(
  partition by sektor, row, scene1, scene2
) cnt

The above window function counts all rows that are in the same partition (“group”) as the current row, given the partition criteria. In other words, all the seats that have the same (scene1, scene2) colouring and that are located in the same (sector, row).

The other window functions are lead and lag, which return a value from a previous or subsequent row, given a specific ordering:

lag (block) over(o),
lead(block) over(o)
-- ...
window o as (
  order by sektor, row, seat
)

Note also the use of the SQL standard WINDOW clause, which is supported only by PostgreSQL and Sybase SQL Anywhere.

In the above snippet, lag() returns the block value of the previous row given the ordering o, whereas lead() would return the next row’s value for block – or NULL, in case of which we’re glad that we used the DISTINCT predicate, before.

Note that you can also optionally supply an additional numeric parameter, to indicate that you want to access the second, third, fifth, or eighth, row backwards or forward.

SQL is your most powerful and underestimated tool

At Data Geekery, we always say that

SQL is a device whose mystery is only exceeded by its powertweet this

If you’ve been following our blog, you may have noticed that we try to evangelise SQL as a great first-class citizen for Java developers. Most of the above features are supported by jOOQ, and translated to your native SQL dialect, if they’re not available.

So, if you haven’t already, listen to Peter Kopfler who was so thrilled after our recent jOOQ/SQL talks in Vienna that he’s now all into studying standards and using PostgreSQL:

Further reading

There was SQL before window functions and SQL after window functions

Tags: , , , , ,

5 responses to “How can I do This? – With SQL of Course!”

  1. Matt Bannert (@learnFromInfo) says :

    yup, awesome indeed! Thanks a lot.

    • Alexander Heim says :

      “All of the above features are supported by jOOQ,” – could you show us how to actually write the code? I would love to use the ‘WITH’ construction in jOOQ but thought, it’s not supported at the moment.

      • lukaseder says :

        I would love to use the ‘WITH’ construction in jOOQ but thought, it’s not supported at the moment.

        We’re (finally!) implementing common table expression support in jOOQ 3.4. As a matter of fact, I just realised that the ROW() constructor currently cannot be used in a SELECT clause like in the PostgreSQL example. This feature is on the roadmap for jOOQ 3.4 as well: https://github.com/jOOQ/jOOQ/issues/2360. Another missing feature is the ability to pass such a ROW() type to the LEAD() and LAG() functions. I have registered another feature request for this: https://github.com/jOOQ/jOOQ/issues/3188 (and I will fix the blog post: “Most of the above features…”)

        An example of the current state of the integration tests for common table expressions can be seen here:

        Inlining common table expression definitions

        create().with("t", "f1", "f2").as(select(val(1), val("a")))
                .select()
                .from(tableByName("t"))
                .fetch();
        

        Using CommonTableExpression types

        CommonTableExpression<Record2<Integer, String>> t1 = name("t1").fields("f1", "f2").as(select(val(1), val("a")));
        CommonTableExpression<Record2<Integer, String>> t2 = name("t2").fields("f3", "f4").as(select(val(2), val("b")));
        
        Result<?> result2 =
        create().with(t1)
                .with(t2)
                .select(
                    t1.field("f1").add(t2.field("f3")).as("add"),
                    t1.field("f2").concat(t2.field("f4")).as("concat"))
                .from(t1, t2)
                .fetch();
        

        Let me know if you’re interested in seeing more, at the moment.

        • Alexander Heim says :

          That’s great news!

          Reading your example, I was a bit struggling to translate it to SQL. Would the first code read as follows in Oracle?

          WITH T AS(SELECT 1 AS F1, ‘A’ AS F2 FROM DUAL )
          SELECT * FROM T

          In this case, yes, maybe another example with real table data would be interesting. Just to reassure me that the expressions within the CTE can be formulated as easy and powerfully as with the usual selects.

          • lukaseder says :

            Almost. The first example would translate to this:

            WITH t(f1, f2) AS (
                SELECT 1, 2 FROM DUAL
            )
            SELECT * FROM t
            

            Unfortunately, as with derived tables in jOOQ, a lot of typesafety is lost when writing common table expressions. I.e. even if you assign a CommonTableExpression to a reusable local variable (as in the second example) there is no typesafe way to dereference T.F1. I think this would only be possible, if there was a compiler plugin or some sort of type-provider to provide a new local type:

            class TRecord implements TableRecord { ... }
            class T implements Table<TRecord> {
                public final TableField<TRecord, Integer> F1;
                public final TableField<TRecord, Integer> F2;
            }
            

            But currently, we’re not aware of this capability in Java.

            In this case, yes, maybe another example with real table data would be interesting.

            Can you provide me with a sample SQL statement, then?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 2,198 other followers

%d bloggers like this: