DroidOS
DroidOS

Reputation: 8890

SQLite find table row where a subset of columns satisfies a specified constraint

I have the following SQLite table

CREATE TABLE visits(urid INTEGER PRIMARY KEY AUTOINCREMENT,
                    hash TEXT,dX INTEGER,dY INTEGER,dZ INTEGER);

Typical content would be

# select * from visits;
  urid |  hash     |  dx   |   dY   |   dZ
 ------+-----------+-------+--------+------
   1   |  'abcd'   |  10   |   10   |   10  
   2   |  'abcd'   |  11   |   11   |   11
   3   |  'bcde'   |   7   |    7   |    7  
   4   |  'abcd'   |  13   |   13   |   13 
   5   |  'defg'   |  20   |   21   |   17

What I need to do here is identify the urid for the table row which satisfies the constraint

hash = 'abcd' AND (nearby >= (abs(dX - tX) + abs(dY - tY) + abs(dZ - tZ))

with the smallest deviation - in the sense of smallest sum of absolute distances

In the present instance with

 nearby = 7
 tX = tY = tZ = 12

there are three rows that meet the above constraint but with different deviations

  urid |  hash     |  dx   |   dY   |   dZ   |   deviation
 ------+-----------+-------+--------+--------+---------------
   1   |  'abcd'   |  10   |   10   |   10   |       6
   2   |  'abcd'   |  11   |   11   |   11   |       3
   4   |  'abcd'   |  12   |   12   |   12   |       3 

in which case I would like to have reported urid = 2 or urid = 3 - I don't actually care which one gets reported.

Left to my own devices I would fetch the full set of matching rows and then dril down to the one that matches my secondary constraint - smallest deviation - in my own Java code. However, I suspect that is not necessary and it can be done in SQL alone. My knowledge of SQL is sadly too limited here. I hope that someone here can put me on the right path.


I now have managed to do the following

CREATE TEMP TABLE h1(v1 INTEGER,v2 INTEGER);
SELECT urid,(SELECT (abs(dX - 12) + abs(dY - 12) + abs(dZ - 12))) devi FROM visits WHERE hash = 'abcd';

which gives

--SELECT * FROM h1 
  urid |  devi     |
-------+-----------+
   1   |    6      |
   2   |    3      |
   4   |    3      | 

following which I issue

select urid from h1 order by v2 asc limit 1;

which yields urid = 2, the result I am after. Whilst this works, I would like to know if there is a better/simpler way of doing this.

Upvotes: 0

Views: 43

Answers (1)

Shawn
Shawn

Reputation: 52354

You're so close! You have all of the components you need, you just have to put them together into a single query.

Consider:

SELECT urid
     , (abs(dx - :tx) + abs(dy - :tx) + abs(dz - :tx)) AS devi
FROM visits
WHERE hash=:hashval AND devi < :nearby
ORDER BY devi
LIMIT 1

Line by line, first you list the rows and computed values you want (:tx is a placeholder; in your code you want to prepare a statement and then bind values to the placeholders before executing the statement) from the visit table.

Then in the WHERE clause you restrict what rows get returned to those matching the particular hash (That column should have an index for best results... CREATE INDEX visits_idx_hash ON visits(hash) for example), and that have a devi that is less than the value of the :nearby placeholder. (I think devi < :nearby is clearer than :nearby >= devi).

Then you say that you want those results sorted in increasing order according to devi, and LIMIT the returned results to a single row because you don't care about any others (If there are no rows that meet the WHERE constraints, nothing is returned).

Upvotes: 1

Related Questions