Search code examples
sqliteselectsql-order-bydense-rank

SQLite order results by smallest difference


In many ways this question follows on from my previous one. I have a table that is pretty much identical

CREATE TABLE IF NOT EXISTS test
(
 id INTEGER PRIMARY KEY,
 a INTEGER NOT NULL,
 b INTEGER NOT NULL,
 c INTEGER NOT NULL,
 d INTEGER NOT NULL,
 weather INTEGER NOT NULL);

in which I would typically have entries such as

INSERT INTO test (a,b,c,d,weather) VALUES(1,2,3,4,30100306);
INSERT INTO test (a,b,c,d,weather) VALUES(1,2,3,4,30140306);
INSERT INTO test (a,b,c,d) VALUES(1,2,5,5,10100306);    
INSERT INTO test (a,b,c,d) VALUES(1,5,5,5,11100306);
INSERT INTO test (a,b,c,d) VALUES(5,5,5,5,21101306);

Typically this table would have multiple rows with the some/all of b, c and d values being identical but with different a and weather values. As per the answer to my other question I can certainly issue

WITH cte AS (SELECT *, DENSE_RANK() OVER (ORDER BY (b=2) + (c=3) + (d=4) DESC) rn FROM test where a = 1) SELECT * FROM cte WHERE rn < 3;

No issues thus far. However, I have one further requirement which arises as a result of the weather column. Although this value is an integer it is in fact a composite where each digit represents a "banded" weather condition. Take for example weather = 20100306. Here 2 represents the wind direction divided up into 45 degree bands on the compass, 0 represents a wind speed range, 1 indicates precipitation as snow etc. What I need to do now while obtaining my ordered results is to allow for weather differences. Take for example the first two rows

INSERT INTO test (a,b,c,d,weather) VALUES(1,2,3,4,30100306);
INSERT INTO test (a,b,c,d,weather) VALUES(1,2,3,4,30140306);

Though otherwise similar they represent rather different weather conditions - the fourth number is four as opposed to 0 indicating a higher precipitation intensity brand. The WITH cte... above would rank the first two rows at the top which is fine. But what if I would rather have the row that differs the least from an incoming "weather condition" of 30130306? I would clearly like to have the second row appearing at the top. Once again, I can live with the "raw" result returned by WITH cte... and then drill down to the right row based on my current "weather condition" in Java. However, once again I find myself thinking that there is perhaps a rather neat way of doing this in SQL that is outwith my skill set. I'd be most obliged to anyone who might be able to tell me how/whether this can be done using just SQL.


Solution

  • You can sort the results 1st by DENSE_RANK() and 2nd by the absolute difference of weather and the incoming "weather condition":

    WITH cte AS (
      SELECT *, 
        DENSE_RANK() OVER (ORDER BY (b=2) + (c=3) + (d=4) DESC) rn 
      FROM test 
      WHERE a = 1
    ) 
    SELECT a,b,c,d,weather 
    FROM cte 
    WHERE rn < 3
    ORDER BY rn, ABS(weather - ?);
    

    Replace ? with the value of that incoming "weather condition".